1 package PVE
::API2
::Qemu
;
12 use Crypt
::OpenSSL
::Random
;
13 use Socket
qw(SOCK_STREAM);
15 use PVE
::APIClient
::LWP
;
17 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
20 use PVE
::Tools
qw(extract_param);
21 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
23 use PVE
::JSONSchema
qw(get_standard_option);
25 use PVE
::ReplicationConfig
;
26 use PVE
::GuestHelpers
qw(assert_tag_permissions);
29 use PVE
::QemuServer
::Cloudinit
;
30 use PVE
::QemuServer
::CPUConfig
;
31 use PVE
::QemuServer
::Drive
;
32 use PVE
::QemuServer
::ImportDisk
;
33 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
34 use PVE
::QemuServer
::Machine
;
35 use PVE
::QemuServer
::Memory
qw(get_current_memory);
36 use PVE
::QemuServer
::PCI
;
37 use PVE
::QemuServer
::USB
;
39 use PVE
::RPCEnvironment
;
40 use PVE
::AccessControl
;
44 use PVE
::API2
::Firewall
::VM
;
45 use PVE
::API2
::Qemu
::Agent
;
46 use PVE
::VZDump
::Plugin
;
47 use PVE
::DataCenterConfig
;
50 use PVE
::StorageTunnel
;
53 if (!$ENV{PVE_GENERATING_DOCS
}) {
54 require PVE
::HA
::Env
::PVE2
;
55 import PVE
::HA
::Env
::PVE2
;
56 require PVE
::HA
::Config
;
57 import PVE
::HA
::Config
;
61 use base
qw(PVE::RESTHandler);
63 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
65 my $resolve_cdrom_alias = sub {
68 if (my $value = $param->{cdrom
}) {
69 $value .= ",media=cdrom" if $value !~ m/media=/;
70 $param->{ide2
} = $value;
71 delete $param->{cdrom
};
75 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
76 my $foreach_volume_with_alloc = sub {
77 my ($param, $func) = @_;
79 for my $opt (sort keys $param->%*) {
80 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
82 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
85 $func->($opt, $drive);
89 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
91 my $check_drive_param = sub {
92 my ($param, $storecfg, $extra_checks) = @_;
94 for my $opt (sort keys $param->%*) {
95 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
97 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
98 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
100 if ($drive->{'import-from'}) {
101 if ($drive->{file
} !~ $NEW_DISK_RE || $3 != 0) {
103 $opt => "'import-from' requires special syntax - ".
104 "use <storage ID>:0,import-from=<source>",
108 if ($opt eq 'efidisk0') {
109 for my $required (qw(efitype pre-enrolled-keys)) {
110 if (!defined($drive->{$required})) {
112 $opt => "need to specify '$required' when using 'import-from'",
116 } elsif ($opt eq 'tpmstate0') {
117 raise_param_exc
({ $opt => "need to specify 'version' when using 'import-from'" })
118 if !defined($drive->{version
});
122 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
124 $extra_checks->($drive) if $extra_checks;
126 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive, 1);
130 my $check_storage_access = sub {
131 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
133 $foreach_volume_with_alloc->($settings, sub {
134 my ($ds, $drive) = @_;
136 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
138 my $volid = $drive->{file
};
139 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
141 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
143 } elsif ($isCDROM && ($volid eq 'cdrom')) {
144 $rpcenv->check($authuser, "/", ['Sys.Console']);
145 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
146 my ($storeid, $size) = ($2 || $default_storage, $3);
147 die "no storage ID specified (and no default storage)\n" if !$storeid;
148 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
149 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
150 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
151 if !$scfg->{content
}->{images
};
153 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
155 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
156 raise_param_exc
({ $ds => "content type needs to be 'images' or 'iso'" })
157 if $vtype ne 'images' && $vtype ne 'iso';
161 if (my $src_image = $drive->{'import-from'}) {
163 if (PVE
::Storage
::parse_volume_id
($src_image, 1)) { # PVE-managed volume
164 (my $vtype, undef, $src_vmid) = PVE
::Storage
::parse_volname
($storecfg, $src_image);
165 raise_param_exc
({ $ds => "$src_image has wrong type '$vtype' - not an image" })
166 if $vtype ne 'images';
169 if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()
170 $rpcenv->check($authuser, "/vms/${src_vmid}", ['VM.Clone']);
172 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $src_image);
177 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
178 if defined($settings->{vmstatestorage
});
181 my $check_storage_access_clone = sub {
182 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
186 PVE
::QemuConfig-
>foreach_volume($conf, sub {
187 my ($ds, $drive) = @_;
189 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
191 my $volid = $drive->{file
};
193 return if !$volid || $volid eq 'none';
196 if ($volid eq 'cdrom') {
197 $rpcenv->check($authuser, "/", ['Sys.Console']);
199 # we simply allow access
200 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
201 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
202 $sharedvm = 0 if !$scfg->{shared
};
206 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
207 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
208 $sharedvm = 0 if !$scfg->{shared
};
210 $sid = $storage if $storage;
211 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
215 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
216 if defined($conf->{vmstatestorage
});
221 my $check_storage_access_migrate = sub {
222 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
224 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
226 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
228 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
229 die "storage '$storage' does not support vm images\n"
230 if !$scfg->{content
}->{images
};
233 my $import_from_volid = sub {
234 my ($storecfg, $src_volid, $dest_info, $vollist) = @_;
236 die "could not get size of $src_volid\n"
237 if !PVE
::Storage
::volume_size_info
($storecfg, $src_volid, 10);
239 die "cannot import from cloudinit disk\n"
240 if PVE
::QemuServer
::Drive
::drive_is_cloudinit
({ file
=> $src_volid });
242 my $src_vmid = (PVE
::Storage
::parse_volname
($storecfg, $src_volid))[2];
244 my $src_vm_state = sub {
245 my $exists = $src_vmid && PVE
::Cluster
::get_vmlist
()->{ids
}->{$src_vmid} ?
1 : 0;
249 eval { PVE
::QemuConfig
::assert_config_exists_on_node
($src_vmid); };
250 die "owner VM $src_vmid not on local node\n" if $@;
251 $runs = PVE
::QemuServer
::Helpers
::vm_running_locally
($src_vmid) || 0;
254 return ($exists, $runs);
257 my ($src_vm_exists, $running) = $src_vm_state->();
259 die "cannot import from '$src_volid' - full clone feature is not supported\n"
260 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $src_volid, undef, $running);
263 my ($src_vm_exists_now, $running_now) = $src_vm_state->();
265 die "owner VM $src_vmid changed state unexpectedly\n"
266 if $src_vm_exists_now != $src_vm_exists || $running_now != $running;
268 my $src_conf = $src_vm_exists_now ? PVE
::QemuConfig-
>load_config($src_vmid) : {};
270 my $src_drive = { file
=> $src_volid };
272 PVE
::QemuConfig-
>foreach_volume($src_conf, sub {
273 my ($ds, $drive) = @_;
275 return if $src_drivename;
277 if ($drive->{file
} eq $src_volid) {
279 $src_drivename = $ds;
285 running
=> $running_now,
286 drivename
=> $src_drivename,
291 my ($src_storeid) = PVE
::Storage
::parse_volume_id
($src_volid);
293 return PVE
::QemuServer
::clone_disk
(
302 PVE
::Storage
::get_bandwidth_limit
('clone', [$src_storeid, $dest_info->{storage
}]),
308 $cloned = PVE
::QemuConfig-
>lock_config_full($src_vmid, 30, $clonefn);
309 } elsif ($src_vmid) {
310 $cloned = PVE
::QemuConfig-
>lock_config_shared($src_vmid, 30, $clonefn);
312 $cloned = $clonefn->();
315 return $cloned->@{qw(file size)};
318 # Note: $pool is only needed when creating a VM, because pool permissions
319 # are automatically inherited if VM already exists inside a pool.
320 my $create_disks = sub {
321 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
328 my ($ds, $disk) = @_;
330 my $volid = $disk->{file
};
331 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
333 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
334 delete $disk->{size
};
335 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
336 } elsif (defined($volname) && $volname eq 'cloudinit') {
337 $storeid = $storeid // $default_storage;
338 die "no storage ID specified (and no default storage)\n" if !$storeid;
341 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
342 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
343 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
345 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
348 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
349 my $name = "vm-$vmid-cloudinit";
353 $fmt = $disk->{format
} // "qcow2";
356 $fmt = $disk->{format
} // "raw";
359 # Initial disk created with 4 MB and aligned to 4MB on regeneration
360 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
361 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
362 $disk->{file
} = $volid;
363 $disk->{media
} = 'cdrom';
364 push @$vollist, $volid;
365 delete $disk->{format
}; # no longer needed
366 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
367 print "$ds: successfully created disk '$res->{$ds}'\n";
368 } elsif ($volid =~ $NEW_DISK_RE) {
369 my ($storeid, $size) = ($2 || $default_storage, $3);
370 die "no storage ID specified (and no default storage)\n" if !$storeid;
372 if (my $source = delete $disk->{'import-from'}) {
375 if (PVE
::Storage
::parse_volume_id
($source, 1)) { # PVE-managed volume
380 format
=> $disk->{format
},
383 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf, $disk)
384 if $ds eq 'efidisk0';
386 ($dst_volid, $size) = eval {
387 $import_from_volid->($storecfg, $source, $dest_info, $vollist);
389 die "cannot import from '$source' - $@" if $@;
391 $source = PVE
::Storage
::abs_filesystem_path
($storecfg, $source, 1);
392 $size = PVE
::Storage
::file_size_info
($source);
393 die "could not get file size of $source\n" if !$size;
395 (undef, $dst_volid) = PVE
::QemuServer
::ImportDisk
::do_import
(
401 format
=> $disk->{format
},
402 'skip-config-update' => 1,
405 push @$vollist, $dst_volid;
408 $disk->{file
} = $dst_volid;
409 $disk->{size
} = $size;
410 delete $disk->{format
}; # no longer needed
411 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
413 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
414 my $fmt = $disk->{format
} || $defformat;
416 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
419 if ($ds eq 'efidisk0') {
420 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
421 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
422 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
423 } elsif ($ds eq 'tpmstate0') {
424 # swtpm can only use raw volumes, and uses a fixed size
425 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
426 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
428 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
430 push @$vollist, $volid;
431 $disk->{file
} = $volid;
432 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
433 delete $disk->{format
}; # no longer needed
434 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
437 print "$ds: successfully created disk '$res->{$ds}'\n";
439 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
441 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
442 die "cannot use volume $volid - content type needs to be 'images' or 'iso'"
443 if $vtype ne 'images' && $vtype ne 'iso';
445 if (PVE
::QemuServer
::Drive
::drive_is_cloudinit
($disk)) {
447 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
448 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
449 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
451 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
456 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
458 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
459 die "volume $volid does not exist\n" if !$size;
460 $disk->{size
} = $size;
462 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
466 eval { $foreach_volume_with_alloc->($settings, $code); };
468 # free allocated images on error
470 syslog
('err', "VM $vmid creating disks failed");
471 foreach my $volid (@$vollist) {
472 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
478 return ($vollist, $res);
481 my $check_cpu_model_access = sub {
482 my ($rpcenv, $authuser, $new, $existing) = @_;
484 return if !defined($new->{cpu
});
486 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
487 return if !$cpu || !$cpu->{cputype
}; # always allow default
488 my $cputype = $cpu->{cputype
};
490 if ($existing && $existing->{cpu
}) {
491 # changing only other settings doesn't require permissions for CPU model
492 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
493 return if $existingCpu->{cputype
} eq $cputype;
496 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
497 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
512 my $memoryoptions = {
518 my $hwtypeoptions = {
531 my $generaloptions = {
538 'migrate_downtime' => 1,
539 'migrate_speed' => 1,
551 my $vmpoweroptions = {
558 'vmstatestorage' => 1,
561 my $cloudinitoptions = {
572 my $check_vm_create_serial_perm = sub {
573 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
575 return 1 if $authuser eq 'root@pam';
577 foreach my $opt (keys %{$param}) {
578 next if $opt !~ m/^serial\d+$/;
580 if ($param->{$opt} eq 'socket') {
581 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
583 die "only root can set '$opt' config for real devices\n";
590 my sub check_usb_perm
{
591 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
593 return 1 if $authuser eq 'root@pam';
595 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
597 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-usb', $value);
598 if ($device->{host
} && $device->{host
} !~ m/^spice$/i) {
599 die "only root can set '$opt' config for real devices\n";
600 } elsif ($device->{mapping
}) {
601 $rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
603 die "either 'host' or 'mapping' must be set.\n";
609 my sub check_vm_create_usb_perm
{
610 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
612 return 1 if $authuser eq 'root@pam';
614 foreach my $opt (keys %{$param}) {
615 next if $opt !~ m/^usb\d+$/;
616 check_usb_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
622 my sub check_hostpci_perm
{
623 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
625 return 1 if $authuser eq 'root@pam';
627 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-hostpci', $value);
628 if ($device->{host
}) {
629 die "only root can set '$opt' config for non-mapped devices\n";
630 } elsif ($device->{mapping
}) {
631 $rpcenv->check_full($authuser, "/mapping/pci/$device->{mapping}", ['Mapping.Use']);
632 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
634 die "either 'host' or 'mapping' must be set.\n";
640 my sub check_vm_create_hostpci_perm
{
641 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
643 return 1 if $authuser eq 'root@pam';
645 foreach my $opt (keys %{$param}) {
646 next if $opt !~ m/^hostpci\d+$/;
647 check_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
653 my $check_vm_modify_config_perm = sub {
654 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
656 return 1 if $authuser eq 'root@pam';
658 foreach my $opt (@$key_list) {
659 # some checks (e.g., disk, serial port, usb) need to be done somewhere
660 # else, as there the permission can be value dependend
661 next if PVE
::QemuServer
::is_valid_drivename
($opt);
662 next if $opt eq 'cdrom';
663 next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
664 next if $opt eq 'tags';
667 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
668 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
669 } elsif ($memoryoptions->{$opt}) {
670 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
671 } elsif ($hwtypeoptions->{$opt}) {
672 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
673 } elsif ($generaloptions->{$opt}) {
674 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
675 # special case for startup since it changes host behaviour
676 if ($opt eq 'startup') {
677 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
679 } elsif ($vmpoweroptions->{$opt}) {
680 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
681 } elsif ($diskoptions->{$opt}) {
682 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
683 } elsif ($opt =~ m/^net\d+$/) {
684 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
685 } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\d+$/) {
686 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
687 } elsif ($opt eq 'vmstate') {
688 # the user needs Disk and PowerMgmt privileges to change the vmstate
689 # also needs privileges on the storage, that will be checked later
690 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
692 # catches args, lock, etc.
693 # new options will be checked here
694 die "only root can set '$opt' config\n";
701 __PACKAGE__-
>register_method({
705 description
=> "Virtual machine index (per node).",
707 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
711 protected
=> 1, # qemu pid files are only readable by root
713 additionalProperties
=> 0,
715 node
=> get_standard_option
('pve-node'),
719 description
=> "Determine the full status of active VMs.",
727 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
729 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
734 my $rpcenv = PVE
::RPCEnvironment
::get
();
735 my $authuser = $rpcenv->get_user();
737 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
740 foreach my $vmid (keys %$vmstatus) {
741 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
743 my $data = $vmstatus->{$vmid};
750 my $parse_restore_archive = sub {
751 my ($storecfg, $archive) = @_;
753 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
757 if (defined($archive_storeid)) {
758 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
759 $res->{volid
} = $archive;
760 if ($scfg->{type
} eq 'pbs') {
761 $res->{type
} = 'pbs';
765 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
766 $res->{type
} = 'file';
767 $res->{path
} = $path;
772 __PACKAGE__-
>register_method({
776 description
=> "Create or restore a virtual machine.",
778 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
779 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
780 "If you create disks you need 'Datastore.AllocateSpace' on any used storage." .
781 "If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.",
782 user
=> 'all', # check inside
787 additionalProperties
=> 0,
788 properties
=> PVE
::QemuServer
::json_config_properties
(
790 node
=> get_standard_option
('pve-node'),
791 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
793 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.",
797 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
799 storage
=> get_standard_option
('pve-storage-id', {
800 description
=> "Default storage.",
802 completion
=> \
&PVE
::QemuServer
::complete_storage
,
807 description
=> "Allow to overwrite existing VM.",
808 requires
=> 'archive',
813 description
=> "Assign a unique random ethernet address.",
814 requires
=> 'archive',
819 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
820 requires
=> 'archive',
824 type
=> 'string', format
=> 'pve-poolid',
825 description
=> "Add the VM to the specified pool.",
828 description
=> "Override I/O bandwidth limit (in KiB/s).",
832 default => 'restore limit from datacenter or storage config',
838 description
=> "Start VM after it was created successfully.",
850 my $rpcenv = PVE
::RPCEnvironment
::get
();
851 my $authuser = $rpcenv->get_user();
853 my $node = extract_param
($param, 'node');
854 my $vmid = extract_param
($param, 'vmid');
856 my $archive = extract_param
($param, 'archive');
857 my $is_restore = !!$archive;
859 my $bwlimit = extract_param
($param, 'bwlimit');
860 my $force = extract_param
($param, 'force');
861 my $pool = extract_param
($param, 'pool');
862 my $start_after_create = extract_param
($param, 'start');
863 my $storage = extract_param
($param, 'storage');
864 my $unique = extract_param
($param, 'unique');
865 my $live_restore = extract_param
($param, 'live-restore');
867 if (defined(my $ssh_keys = $param->{sshkeys
})) {
868 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
869 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
872 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
873 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
875 PVE
::Cluster
::check_cfs_quorum
();
877 my $filename = PVE
::QemuConfig-
>config_file($vmid);
878 my $storecfg = PVE
::Storage
::config
();
880 if (defined($pool)) {
881 $rpcenv->check_pool_exist($pool);
884 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
885 if defined($storage);
887 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
889 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
891 } elsif ($archive && $force && (-f
$filename) &&
892 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
893 # OK: user has VM.Backup permissions and wants to restore an existing VM
899 for my $opt (sort keys $param->%*) {
900 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
901 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
905 if ($archive eq '-') {
906 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
907 $archive = { type
=> 'pipe' };
909 PVE
::Storage
::check_volume_access
(
918 $archive = $parse_restore_archive->($storecfg, $archive);
922 if (scalar(keys $param->%*) > 0) {
923 &$resolve_cdrom_alias($param);
925 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
927 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
929 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
930 check_vm_create_usb_perm
($rpcenv, $authuser, $vmid, $pool, $param);
931 check_vm_create_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $param);
933 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
934 &$check_cpu_model_access($rpcenv, $authuser, $param);
936 $check_drive_param->($param, $storecfg);
938 PVE
::QemuServer
::add_random_macs
($param);
941 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
943 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
944 die "$emsg $@" if $@;
946 my $restored_data = 0;
947 my $restorefn = sub {
948 my $conf = PVE
::QemuConfig-
>load_config($vmid);
950 PVE
::QemuConfig-
>check_protection($conf, $emsg);
952 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
955 my $restore_options = {
960 live
=> $live_restore,
961 override_conf
=> $param,
963 if (my $volid = $archive->{volid
}) {
964 # best effort, real check is after restoring!
966 my $old_conf = PVE
::Storage
::extract_vzdump_config
($storecfg, $volid);
967 PVE
::QemuServer
::restore_merge_config
("backup/qemu-server/$vmid.conf", $old_conf, $param);
970 warn "Could not extract backed up config: $@\n";
971 warn "Skipping early checks!\n";
973 PVE
::QemuServer
::check_restore_permissions
($rpcenv, $authuser, $merged);
976 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
977 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
979 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
980 } elsif ($archive->{type
} eq 'pbs') {
981 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
983 die "unknown backup archive type\n";
987 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
988 # Convert restored VM to template if backup was VM template
989 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
990 warn "Convert to template.\n";
991 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
996 # ensure no old replication state are exists
997 PVE
::ReplicationState
::delete_guest_states
($vmid);
999 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1001 if ($start_after_create && !$live_restore) {
1002 print "Execute autostart\n";
1003 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
1008 my $createfn = sub {
1009 # ensure no old replication state are exists
1010 PVE
::ReplicationState
::delete_guest_states
($vmid);
1014 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1016 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
1020 ($vollist, my $created_opts) = $create_disks->(
1031 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1033 if (!$conf->{boot
}) {
1034 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
1035 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
1038 # auto generate uuid if user did not specify smbios1 option
1039 if (!$conf->{smbios1
}) {
1040 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
1043 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
1044 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
1047 my $machine = $conf->{machine
};
1048 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
1049 # always pin Windows' machine version on create, they get to easily confused
1050 if (PVE
::QemuServer
::Helpers
::windows_version
($conf->{ostype
})) {
1051 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
1055 PVE
::QemuConfig-
>write_config($vmid, $conf);
1061 foreach my $volid (@$vollist) {
1062 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1068 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
1071 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1073 if ($start_after_create) {
1074 print "Execute autostart\n";
1075 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
1080 my ($code, $worker_name);
1082 $worker_name = 'qmrestore';
1084 eval { $restorefn->() };
1086 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
1088 if ($restored_data) {
1089 warn "error after data was restored, VM disks should be OK but config may "
1090 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1092 warn "error before or during data restore, some or all disks were not "
1093 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1099 $worker_name = 'qmcreate';
1101 eval { $createfn->() };
1104 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1105 unlink($conffile) or die "failed to remove config file: $!\n";
1113 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1116 __PACKAGE__-
>register_method({
1121 description
=> "Directory index",
1126 additionalProperties
=> 0,
1128 node
=> get_standard_option
('pve-node'),
1129 vmid
=> get_standard_option
('pve-vmid'),
1137 subdir
=> { type
=> 'string' },
1140 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1146 { subdir
=> 'config' },
1147 { subdir
=> 'cloudinit' },
1148 { subdir
=> 'pending' },
1149 { subdir
=> 'status' },
1150 { subdir
=> 'unlink' },
1151 { subdir
=> 'vncproxy' },
1152 { subdir
=> 'termproxy' },
1153 { subdir
=> 'migrate' },
1154 { subdir
=> 'resize' },
1155 { subdir
=> 'move' },
1156 { subdir
=> 'rrd' },
1157 { subdir
=> 'rrddata' },
1158 { subdir
=> 'monitor' },
1159 { subdir
=> 'agent' },
1160 { subdir
=> 'snapshot' },
1161 { subdir
=> 'spiceproxy' },
1162 { subdir
=> 'sendkey' },
1163 { subdir
=> 'firewall' },
1164 { subdir
=> 'mtunnel' },
1165 { subdir
=> 'remote_migrate' },
1171 __PACKAGE__-
>register_method ({
1172 subclass
=> "PVE::API2::Firewall::VM",
1173 path
=> '{vmid}/firewall',
1176 __PACKAGE__-
>register_method ({
1177 subclass
=> "PVE::API2::Qemu::Agent",
1178 path
=> '{vmid}/agent',
1181 __PACKAGE__-
>register_method({
1183 path
=> '{vmid}/rrd',
1185 protected
=> 1, # fixme: can we avoid that?
1187 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1189 description
=> "Read VM RRD statistics (returns PNG)",
1191 additionalProperties
=> 0,
1193 node
=> get_standard_option
('pve-node'),
1194 vmid
=> get_standard_option
('pve-vmid'),
1196 description
=> "Specify the time frame you are interested in.",
1198 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1201 description
=> "The list of datasources you want to display.",
1202 type
=> 'string', format
=> 'pve-configid-list',
1205 description
=> "The RRD consolidation function",
1207 enum
=> [ 'AVERAGE', 'MAX' ],
1215 filename
=> { type
=> 'string' },
1221 return PVE
::RRD
::create_rrd_graph
(
1222 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1223 $param->{ds
}, $param->{cf
});
1227 __PACKAGE__-
>register_method({
1229 path
=> '{vmid}/rrddata',
1231 protected
=> 1, # fixme: can we avoid that?
1233 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1235 description
=> "Read VM RRD statistics",
1237 additionalProperties
=> 0,
1239 node
=> get_standard_option
('pve-node'),
1240 vmid
=> get_standard_option
('pve-vmid'),
1242 description
=> "Specify the time frame you are interested in.",
1244 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1247 description
=> "The RRD consolidation function",
1249 enum
=> [ 'AVERAGE', 'MAX' ],
1264 return PVE
::RRD
::create_rrd_data
(
1265 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1269 __PACKAGE__-
>register_method({
1270 name
=> 'vm_config',
1271 path
=> '{vmid}/config',
1274 description
=> "Get the virtual machine configuration with pending configuration " .
1275 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1277 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1280 additionalProperties
=> 0,
1282 node
=> get_standard_option
('pve-node'),
1283 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1285 description
=> "Get current values (instead of pending values).",
1290 snapshot
=> get_standard_option
('pve-snapshot-name', {
1291 description
=> "Fetch config values from given snapshot.",
1294 my ($cmd, $pname, $cur, $args) = @_;
1295 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1301 description
=> "The VM configuration.",
1303 properties
=> PVE
::QemuServer
::json_config_properties
({
1306 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1313 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1314 current
=> "cannot use 'snapshot' parameter with 'current'"})
1315 if ($param->{snapshot
} && $param->{current
});
1318 if ($param->{snapshot
}) {
1319 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1321 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1323 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1328 __PACKAGE__-
>register_method({
1329 name
=> 'vm_pending',
1330 path
=> '{vmid}/pending',
1333 description
=> "Get the virtual machine configuration with both current and pending values.",
1335 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1338 additionalProperties
=> 0,
1340 node
=> get_standard_option
('pve-node'),
1341 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1350 description
=> "Configuration option name.",
1354 description
=> "Current value.",
1359 description
=> "Pending value.",
1364 description
=> "Indicates a pending delete request if present and not 0. " .
1365 "The value 2 indicates a force-delete request.",
1377 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1379 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1381 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1382 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1384 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1387 __PACKAGE__-
>register_method({
1388 name
=> 'cloudinit_pending',
1389 path
=> '{vmid}/cloudinit',
1392 description
=> "Get the cloudinit configuration with both current and pending values.",
1394 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1397 additionalProperties
=> 0,
1399 node
=> get_standard_option
('pve-node'),
1400 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1409 description
=> "Configuration option name.",
1413 description
=> "Value as it was used to generate the current cloudinit image.",
1418 description
=> "The new pending value.",
1423 description
=> "Indicates a pending delete request if present and not 0. ",
1435 my $vmid = $param->{vmid
};
1436 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1438 my $ci = $conf->{cloudinit
};
1440 $conf->{cipassword
} = '**********' if exists $conf->{cipassword
};
1441 $ci->{cipassword
} = '**********' if exists $ci->{cipassword
};
1445 # All the values that got added
1446 my $added = delete($ci->{added
}) // '';
1447 for my $key (PVE
::Tools
::split_list
($added)) {
1448 push @$res, { key
=> $key, pending
=> $conf->{$key} };
1451 # All already existing values (+ their new value, if it exists)
1452 for my $opt (keys %$cloudinitoptions) {
1453 next if !$conf->{$opt};
1454 next if $added =~ m/$opt/;
1459 if (my $pending = $ci->{$opt}) {
1460 $item->{value
} = $pending;
1461 $item->{pending
} = $conf->{$opt};
1463 $item->{value
} = $conf->{$opt},
1469 # Now, we'll find the deleted ones
1470 for my $opt (keys %$ci) {
1471 next if $conf->{$opt};
1472 push @$res, { key
=> $opt, delete => 1 };
1478 __PACKAGE__-
>register_method({
1479 name
=> 'cloudinit_update',
1480 path
=> '{vmid}/cloudinit',
1484 description
=> "Regenerate and change cloudinit config drive.",
1486 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Cloudinit']],
1489 additionalProperties
=> 0,
1491 node
=> get_standard_option
('pve-node'),
1492 vmid
=> get_standard_option
('pve-vmid'),
1495 returns
=> { type
=> 'null' },
1499 my $rpcenv = PVE
::RPCEnvironment
::get
();
1500 my $authuser = $rpcenv->get_user();
1502 my $vmid = extract_param
($param, 'vmid');
1504 PVE
::QemuConfig-
>lock_config($vmid, sub {
1505 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1506 PVE
::QemuConfig-
>check_lock($conf);
1508 my $storecfg = PVE
::Storage
::config
();
1509 PVE
::QemuServer
::vmconfig_update_cloudinit_drive
($storecfg, $conf, $vmid);
1514 # POST/PUT {vmid}/config implementation
1516 # The original API used PUT (idempotent) an we assumed that all operations
1517 # are fast. But it turned out that almost any configuration change can
1518 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1519 # time to complete and have side effects (not idempotent).
1521 # The new implementation uses POST and forks a worker process. We added
1522 # a new option 'background_delay'. If specified we wait up to
1523 # 'background_delay' second for the worker task to complete. It returns null
1524 # if the task is finished within that time, else we return the UPID.
1526 my $update_vm_api = sub {
1527 my ($param, $sync) = @_;
1529 my $rpcenv = PVE
::RPCEnvironment
::get
();
1531 my $authuser = $rpcenv->get_user();
1533 my $node = extract_param
($param, 'node');
1535 my $vmid = extract_param
($param, 'vmid');
1537 my $digest = extract_param
($param, 'digest');
1539 my $background_delay = extract_param
($param, 'background_delay');
1541 my $skip_cloud_init = extract_param
($param, 'skip_cloud_init');
1543 if (defined(my $cipassword = $param->{cipassword
})) {
1544 # Same logic as in cloud-init (but with the regex fixed...)
1545 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1546 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1549 my @paramarr = (); # used for log message
1550 foreach my $key (sort keys %$param) {
1551 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1552 push @paramarr, "-$key", $value;
1555 my $skiplock = extract_param
($param, 'skiplock');
1556 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1557 if $skiplock && $authuser ne 'root@pam';
1559 my $delete_str = extract_param
($param, 'delete');
1561 my $revert_str = extract_param
($param, 'revert');
1563 my $force = extract_param
($param, 'force');
1565 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1566 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1567 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1570 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
1571 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1573 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1575 my $storecfg = PVE
::Storage
::config
();
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
})) {
1697 my $memory = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
};
1698 my $maxmem = get_current_memory
($memory);
1699 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1701 die "balloon value too large (must be smaller than assigned memory)\n"
1702 if $balloon && $balloon > $maxmem;
1705 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1709 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1711 # write updates to pending section
1713 my $modified = {}; # record what $option we modify
1716 if (my $boot = $conf->{boot
}) {
1717 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1718 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1720 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1722 my $check_drive_perms = sub {
1723 my ($opt, $val) = @_;
1724 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1725 if (PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
1726 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Cloudinit', 'VM.Config.CDROM']);
1727 } elsif (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) { # CDROM
1728 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1730 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1735 foreach my $opt (@delete) {
1736 $modified->{$opt} = 1;
1737 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1739 # value of what we want to delete, independent if pending or not
1740 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1741 if (!defined($val)) {
1742 warn "cannot delete '$opt' - not set in current configuration!\n";
1743 $modified->{$opt} = 0;
1746 my $is_pending_val = defined($conf->{pending
}->{$opt});
1747 delete $conf->{pending
}->{$opt};
1749 # remove from bootorder if necessary
1750 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1751 @bootorder = grep {$_ ne $opt} @bootorder;
1752 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1753 $modified->{boot
} = 1;
1756 if ($opt =~ m/^unused/) {
1757 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1758 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1759 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1760 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1761 delete $conf->{$opt};
1762 PVE
::QemuConfig-
>write_config($vmid, $conf);
1764 } elsif ($opt eq 'vmstate') {
1765 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1766 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1767 delete $conf->{$opt};
1768 PVE
::QemuConfig-
>write_config($vmid, $conf);
1770 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1771 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1772 $check_drive_perms->($opt, $val);
1773 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1775 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1776 PVE
::QemuConfig-
>write_config($vmid, $conf);
1777 } elsif ($opt =~ m/^serial\d+$/) {
1778 if ($val eq 'socket') {
1779 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1780 } elsif ($authuser ne 'root@pam') {
1781 die "only root can delete '$opt' config for real devices\n";
1783 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1784 PVE
::QemuConfig-
>write_config($vmid, $conf);
1785 } elsif ($opt =~ m/^usb\d+$/) {
1786 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1787 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1788 PVE
::QemuConfig-
>write_config($vmid, $conf);
1789 } elsif ($opt =~ m/^hostpci\d+$/) {
1790 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1791 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1792 PVE
::QemuConfig-
>write_config($vmid, $conf);
1793 } elsif ($opt eq 'tags') {
1794 assert_tag_permissions
($vmid, $val, '', $rpcenv, $authuser);
1795 delete $conf->{$opt};
1796 PVE
::QemuConfig-
>write_config($vmid, $conf);
1797 } elsif ($opt =~ m/^net\d+$/) {
1798 if ($conf->{$opt}) {
1799 PVE
::QemuServer
::check_bridge_access
(
1802 { $opt => $conf->{$opt} },
1805 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1806 PVE
::QemuConfig-
>write_config($vmid, $conf);
1808 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1809 PVE
::QemuConfig-
>write_config($vmid, $conf);
1813 foreach my $opt (keys %$param) { # add/change
1814 $modified->{$opt} = 1;
1815 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1816 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1818 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1820 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1822 if ($conf->{$opt}) {
1823 $check_drive_perms->($opt, $conf->{$opt});
1827 $check_drive_perms->($opt, $param->{$opt});
1828 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1829 if defined($conf->{pending
}->{$opt});
1831 my (undef, $created_opts) = $create_disks->(
1839 {$opt => $param->{$opt}},
1841 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1843 # default legacy boot order implies all cdroms anyway
1845 # append new CD drives to bootorder to mark them bootable
1846 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1847 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1848 push @bootorder, $opt;
1849 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1850 $modified->{boot
} = 1;
1853 } elsif ($opt =~ m/^serial\d+/) {
1854 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1855 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1856 } elsif ($authuser ne 'root@pam') {
1857 die "only root can modify '$opt' config for real devices\n";
1859 $conf->{pending
}->{$opt} = $param->{$opt};
1860 } elsif ($opt =~ m/^usb\d+/) {
1861 if (my $olddevice = $conf->{$opt}) {
1862 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});
1864 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1865 $conf->{pending
}->{$opt} = $param->{$opt};
1866 } elsif ($opt =~ m/^hostpci\d+$/) {
1867 if (my $oldvalue = $conf->{$opt}) {
1868 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);
1870 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1871 $conf->{pending
}->{$opt} = $param->{$opt};
1872 } elsif ($opt eq 'tags') {
1873 assert_tag_permissions
($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
1874 $conf->{pending
}->{$opt} = PVE
::GuestHelpers
::get_unique_tags
($param->{$opt});
1875 } elsif ($opt =~ m/^net\d+$/) {
1876 if ($conf->{$opt}) {
1877 PVE
::QemuServer
::check_bridge_access
(
1880 { $opt => $conf->{$opt} },
1883 $conf->{pending
}->{$opt} = $param->{$opt};
1885 $conf->{pending
}->{$opt} = $param->{$opt};
1887 if ($opt eq 'boot') {
1888 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1889 if ($new_bootcfg->{order
}) {
1890 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1891 for my $dev (@devs) {
1892 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1893 my $deleted = grep {$_ eq $dev} @delete;
1894 die "invalid bootorder: device '$dev' does not exist'\n"
1895 if !$exists || $deleted;
1898 # remove legacy boot order settings if new one set
1899 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1900 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1901 if $conf->{bootdisk
};
1905 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1906 PVE
::QemuConfig-
>write_config($vmid, $conf);
1909 # remove pending changes when nothing changed
1910 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1911 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1912 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1914 return if !scalar(keys %{$conf->{pending
}});
1916 my $running = PVE
::QemuServer
::check_running
($vmid);
1918 # apply pending changes
1920 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1924 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1926 # cloud_init must be skipped if we are in an incoming, remote live migration
1927 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors, $skip_cloud_init);
1929 raise_param_exc
($errors) if scalar(keys %$errors);
1938 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1940 if ($background_delay) {
1942 # Note: It would be better to do that in the Event based HTTPServer
1943 # to avoid blocking call to sleep.
1945 my $end_time = time() + $background_delay;
1947 my $task = PVE
::Tools
::upid_decode
($upid);
1950 while (time() < $end_time) {
1951 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1953 sleep(1); # this gets interrupted when child process ends
1957 my $status = PVE
::Tools
::upid_read_status
($upid);
1958 return if !PVE
::Tools
::upid_status_is_error
($status);
1959 die "failed to update VM $vmid: $status\n";
1967 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1970 my $vm_config_perm_list = [
1975 'VM.Config.Network',
1977 'VM.Config.Options',
1978 'VM.Config.Cloudinit',
1981 __PACKAGE__-
>register_method({
1982 name
=> 'update_vm_async',
1983 path
=> '{vmid}/config',
1987 description
=> "Set virtual machine options (asynchrounous API).",
1989 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1992 additionalProperties
=> 0,
1993 properties
=> PVE
::QemuServer
::json_config_properties
(
1995 node
=> get_standard_option
('pve-node'),
1996 vmid
=> get_standard_option
('pve-vmid'),
1997 skiplock
=> get_standard_option
('skiplock'),
1999 type
=> 'string', format
=> 'pve-configid-list',
2000 description
=> "A list of settings you want to delete.",
2004 type
=> 'string', format
=> 'pve-configid-list',
2005 description
=> "Revert a pending change.",
2010 description
=> $opt_force_description,
2012 requires
=> 'delete',
2016 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2020 background_delay
=> {
2022 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
2028 1, # with_disk_alloc
2035 code
=> $update_vm_api,
2038 __PACKAGE__-
>register_method({
2039 name
=> 'update_vm',
2040 path
=> '{vmid}/config',
2044 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
2046 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
2049 additionalProperties
=> 0,
2050 properties
=> PVE
::QemuServer
::json_config_properties
(
2052 node
=> get_standard_option
('pve-node'),
2053 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2054 skiplock
=> get_standard_option
('skiplock'),
2056 type
=> 'string', format
=> 'pve-configid-list',
2057 description
=> "A list of settings you want to delete.",
2061 type
=> 'string', format
=> 'pve-configid-list',
2062 description
=> "Revert a pending change.",
2067 description
=> $opt_force_description,
2069 requires
=> 'delete',
2073 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2078 1, # with_disk_alloc
2081 returns
=> { type
=> 'null' },
2084 &$update_vm_api($param, 1);
2089 __PACKAGE__-
>register_method({
2090 name
=> 'destroy_vm',
2095 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
2096 ." and firewall rules",
2098 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2101 additionalProperties
=> 0,
2103 node
=> get_standard_option
('pve-node'),
2104 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2105 skiplock
=> get_standard_option
('skiplock'),
2108 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
2111 'destroy-unreferenced-disks' => {
2113 description
=> "If set, destroy additionally all disks not referenced in the config"
2114 ." but with a matching VMID from all enabled storages.",
2126 my $rpcenv = PVE
::RPCEnvironment
::get
();
2127 my $authuser = $rpcenv->get_user();
2128 my $vmid = $param->{vmid
};
2130 my $skiplock = $param->{skiplock
};
2131 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2132 if $skiplock && $authuser ne 'root@pam';
2134 my $early_checks = sub {
2136 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2137 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
2139 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
2141 if (!$param->{purge
}) {
2142 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
2144 # don't allow destroy if with replication jobs but no purge param
2145 my $repl_conf = PVE
::ReplicationConfig-
>new();
2146 $repl_conf->check_for_existing_jobs($vmid);
2149 die "VM $vmid is running - destroy failed\n"
2150 if PVE
::QemuServer
::check_running
($vmid);
2160 my $storecfg = PVE
::Storage
::config
();
2162 syslog
('info', "destroy VM $vmid: $upid\n");
2163 PVE
::QemuConfig-
>lock_config($vmid, sub {
2164 # repeat, config might have changed
2165 my $ha_managed = $early_checks->();
2167 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
2169 PVE
::QemuServer
::destroy_vm
(
2172 $skiplock, { lock => 'destroyed' },
2173 $purge_unreferenced,
2176 PVE
::AccessControl
::remove_vm_access
($vmid);
2177 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2178 if ($param->{purge
}) {
2179 print "purging VM $vmid from related configurations..\n";
2180 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2181 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2184 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2185 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2189 # only now remove the zombie config, else we can have reuse race
2190 PVE
::QemuConfig-
>destroy_config($vmid);
2194 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2197 __PACKAGE__-
>register_method({
2199 path
=> '{vmid}/unlink',
2203 description
=> "Unlink/delete disk images.",
2205 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2208 additionalProperties
=> 0,
2210 node
=> get_standard_option
('pve-node'),
2211 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2213 type
=> 'string', format
=> 'pve-configid-list',
2214 description
=> "A list of disk IDs you want to delete.",
2218 description
=> $opt_force_description,
2223 returns
=> { type
=> 'null'},
2227 $param->{delete} = extract_param
($param, 'idlist');
2229 __PACKAGE__-
>update_vm($param);
2234 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2235 my $gen_rand_chars = sub {
2238 die "invalid length $length" if $length < 1;
2240 my $min = ord('!'); # first printable ascii
2242 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2243 die "failed to generate random bytes!\n"
2246 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2253 __PACKAGE__-
>register_method({
2255 path
=> '{vmid}/vncproxy',
2259 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2261 description
=> "Creates a TCP VNC proxy connections.",
2263 additionalProperties
=> 0,
2265 node
=> get_standard_option
('pve-node'),
2266 vmid
=> get_standard_option
('pve-vmid'),
2270 description
=> "Prepare for websocket upgrade (only required when using "
2271 ."serial terminal, otherwise upgrade is always possible).",
2273 'generate-password' => {
2277 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2282 additionalProperties
=> 0,
2284 user
=> { type
=> 'string' },
2285 ticket
=> { type
=> 'string' },
2288 description
=> "Returned if requested with 'generate-password' param."
2289 ." Consists of printable ASCII characters ('!' .. '~').",
2292 cert
=> { type
=> 'string' },
2293 port
=> { type
=> 'integer' },
2294 upid
=> { type
=> 'string' },
2300 my $rpcenv = PVE
::RPCEnvironment
::get
();
2302 my $authuser = $rpcenv->get_user();
2304 my $vmid = $param->{vmid
};
2305 my $node = $param->{node
};
2306 my $websocket = $param->{websocket
};
2308 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2312 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2313 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2316 my $authpath = "/vms/$vmid";
2318 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2319 my $password = $ticket;
2320 if ($param->{'generate-password'}) {
2321 $password = $gen_rand_chars->(8);
2324 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2330 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2331 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2332 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2333 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2334 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2336 $family = PVE
::Tools
::get_host_address_family
($node);
2339 my $port = PVE
::Tools
::next_vnc_port
($family);
2346 syslog
('info', "starting vnc proxy $upid\n");
2350 if (defined($serial)) {
2352 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2354 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2355 '-timeout', $timeout, '-authpath', $authpath,
2356 '-perm', 'Sys.Console'];
2358 if ($param->{websocket
}) {
2359 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2360 push @$cmd, '-notls', '-listen', 'localhost';
2363 push @$cmd, '-c', @$remcmd, @$termcmd;
2365 PVE
::Tools
::run_command
($cmd);
2369 $ENV{LC_PVE_TICKET
} = $password; # set ticket with "qm vncproxy"
2371 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2373 my $sock = IO
::Socket
::IP-
>new(
2378 GetAddrInfoFlags
=> 0,
2379 ) or die "failed to create socket: $!\n";
2380 # Inside the worker we shouldn't have any previous alarms
2381 # running anyway...:
2383 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2385 accept(my $cli, $sock) or die "connection failed: $!\n";
2388 if (PVE
::Tools
::run_command
($cmd,
2389 output
=> '>&'.fileno($cli),
2390 input
=> '<&'.fileno($cli),
2393 die "Failed to run vncproxy.\n";
2400 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2402 PVE
::Tools
::wait_for_vnc_port
($port);
2411 $res->{password
} = $password if $param->{'generate-password'};
2416 __PACKAGE__-
>register_method({
2417 name
=> 'termproxy',
2418 path
=> '{vmid}/termproxy',
2422 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2424 description
=> "Creates a TCP proxy connections.",
2426 additionalProperties
=> 0,
2428 node
=> get_standard_option
('pve-node'),
2429 vmid
=> get_standard_option
('pve-vmid'),
2433 enum
=> [qw(serial0 serial1 serial2 serial3)],
2434 description
=> "opens a serial terminal (defaults to display)",
2439 additionalProperties
=> 0,
2441 user
=> { type
=> 'string' },
2442 ticket
=> { type
=> 'string' },
2443 port
=> { type
=> 'integer' },
2444 upid
=> { type
=> 'string' },
2450 my $rpcenv = PVE
::RPCEnvironment
::get
();
2452 my $authuser = $rpcenv->get_user();
2454 my $vmid = $param->{vmid
};
2455 my $node = $param->{node
};
2456 my $serial = $param->{serial
};
2458 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2460 if (!defined($serial)) {
2462 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2463 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2467 my $authpath = "/vms/$vmid";
2469 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2474 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2475 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2476 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2477 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2478 push @$remcmd, '--';
2480 $family = PVE
::Tools
::get_host_address_family
($node);
2483 my $port = PVE
::Tools
::next_vnc_port
($family);
2485 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2486 push @$termcmd, '-iface', $serial if $serial;
2491 syslog
('info', "starting qemu termproxy $upid\n");
2493 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2494 '--perm', 'VM.Console', '--'];
2495 push @$cmd, @$remcmd, @$termcmd;
2497 PVE
::Tools
::run_command
($cmd);
2500 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2502 PVE
::Tools
::wait_for_vnc_port
($port);
2512 __PACKAGE__-
>register_method({
2513 name
=> 'vncwebsocket',
2514 path
=> '{vmid}/vncwebsocket',
2517 description
=> "You also need to pass a valid ticket (vncticket).",
2518 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2520 description
=> "Opens a weksocket for VNC traffic.",
2522 additionalProperties
=> 0,
2524 node
=> get_standard_option
('pve-node'),
2525 vmid
=> get_standard_option
('pve-vmid'),
2527 description
=> "Ticket from previous call to vncproxy.",
2532 description
=> "Port number returned by previous vncproxy call.",
2542 port
=> { type
=> 'string' },
2548 my $rpcenv = PVE
::RPCEnvironment
::get
();
2550 my $authuser = $rpcenv->get_user();
2552 my $vmid = $param->{vmid
};
2553 my $node = $param->{node
};
2555 my $authpath = "/vms/$vmid";
2557 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2559 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2561 # Note: VNC ports are acessible from outside, so we do not gain any
2562 # security if we verify that $param->{port} belongs to VM $vmid. This
2563 # check is done by verifying the VNC ticket (inside VNC protocol).
2565 my $port = $param->{port
};
2567 return { port
=> $port };
2570 __PACKAGE__-
>register_method({
2571 name
=> 'spiceproxy',
2572 path
=> '{vmid}/spiceproxy',
2577 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2579 description
=> "Returns a SPICE configuration to connect to the VM.",
2581 additionalProperties
=> 0,
2583 node
=> get_standard_option
('pve-node'),
2584 vmid
=> get_standard_option
('pve-vmid'),
2585 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2588 returns
=> get_standard_option
('remote-viewer-config'),
2592 my $rpcenv = PVE
::RPCEnvironment
::get
();
2594 my $authuser = $rpcenv->get_user();
2596 my $vmid = $param->{vmid
};
2597 my $node = $param->{node
};
2598 my $proxy = $param->{proxy
};
2600 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2601 my $title = "VM $vmid";
2602 $title .= " - ". $conf->{name
} if $conf->{name
};
2604 my $port = PVE
::QemuServer
::spice_port
($vmid);
2606 my ($ticket, undef, $remote_viewer_config) =
2607 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2609 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2610 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2612 return $remote_viewer_config;
2615 __PACKAGE__-
>register_method({
2617 path
=> '{vmid}/status',
2620 description
=> "Directory index",
2625 additionalProperties
=> 0,
2627 node
=> get_standard_option
('pve-node'),
2628 vmid
=> get_standard_option
('pve-vmid'),
2636 subdir
=> { type
=> 'string' },
2639 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2645 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2648 { subdir
=> 'current' },
2649 { subdir
=> 'start' },
2650 { subdir
=> 'stop' },
2651 { subdir
=> 'reset' },
2652 { subdir
=> 'shutdown' },
2653 { subdir
=> 'suspend' },
2654 { subdir
=> 'reboot' },
2660 __PACKAGE__-
>register_method({
2661 name
=> 'vm_status',
2662 path
=> '{vmid}/status/current',
2665 protected
=> 1, # qemu pid files are only readable by root
2666 description
=> "Get virtual machine status.",
2668 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2671 additionalProperties
=> 0,
2673 node
=> get_standard_option
('pve-node'),
2674 vmid
=> get_standard_option
('pve-vmid'),
2680 %$PVE::QemuServer
::vmstatus_return_properties
,
2682 description
=> "HA manager service status.",
2686 description
=> "QEMU VGA configuration supports spice.",
2691 description
=> "QEMU Guest Agent is enabled in config.",
2701 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2703 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2704 my $status = $vmstatus->{$param->{vmid
}};
2706 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2709 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2710 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2711 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2712 $status->{spice
} = 1 if $spice;
2714 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2719 __PACKAGE__-
>register_method({
2721 path
=> '{vmid}/status/start',
2725 description
=> "Start virtual machine.",
2727 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2730 additionalProperties
=> 0,
2732 node
=> get_standard_option
('pve-node'),
2733 vmid
=> get_standard_option
('pve-vmid',
2734 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2735 skiplock
=> get_standard_option
('skiplock'),
2736 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2737 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2740 enum
=> ['secure', 'insecure'],
2741 description
=> "Migration traffic is encrypted using an SSH " .
2742 "tunnel by default. On secure, completely private networks " .
2743 "this can be disabled to increase performance.",
2746 migration_network
=> {
2747 type
=> 'string', format
=> 'CIDR',
2748 description
=> "CIDR of the (sub) network that is used for migration.",
2751 machine
=> get_standard_option
('pve-qemu-machine'),
2753 description
=> "Override QEMU's -cpu argument with the given string.",
2757 targetstorage
=> get_standard_option
('pve-targetstorage'),
2759 description
=> "Wait maximal timeout seconds.",
2762 default => 'max(30, vm memory in GiB)',
2773 my $rpcenv = PVE
::RPCEnvironment
::get
();
2774 my $authuser = $rpcenv->get_user();
2776 my $node = extract_param
($param, 'node');
2777 my $vmid = extract_param
($param, 'vmid');
2778 my $timeout = extract_param
($param, 'timeout');
2779 my $machine = extract_param
($param, 'machine');
2781 my $get_root_param = sub {
2782 my $value = extract_param
($param, $_[0]);
2783 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2784 if $value && $authuser ne 'root@pam';
2788 my $stateuri = $get_root_param->('stateuri');
2789 my $skiplock = $get_root_param->('skiplock');
2790 my $migratedfrom = $get_root_param->('migratedfrom');
2791 my $migration_type = $get_root_param->('migration_type');
2792 my $migration_network = $get_root_param->('migration_network');
2793 my $targetstorage = $get_root_param->('targetstorage');
2794 my $force_cpu = $get_root_param->('force-cpu');
2798 if ($targetstorage) {
2799 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2801 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2802 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2806 # read spice ticket from STDIN
2808 my $nbd_protocol_version = 0;
2809 my $replicated_volumes = {};
2810 my $offline_volumes = {};
2811 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2812 while (defined(my $line = <STDIN
>)) {
2814 if ($line =~ m/^spice_ticket: (.+)$/) {
2816 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2817 $nbd_protocol_version = $1;
2818 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2819 $replicated_volumes->{$1} = 1;
2820 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2821 $offline_volumes->{tpmstate0
} = $1;
2822 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2823 $offline_volumes->{$1} = $2;
2824 } elsif (!$spice_ticket) {
2825 # fallback for old source node
2826 $spice_ticket = $line;
2828 warn "unknown 'start' parameter on STDIN: '$line'\n";
2833 PVE
::Cluster
::check_cfs_quorum
();
2835 my $storecfg = PVE
::Storage
::config
();
2837 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2841 print "Requesting HA start for VM $vmid\n";
2843 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2844 PVE
::Tools
::run_command
($cmd);
2848 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2855 syslog
('info', "start VM $vmid: $upid\n");
2857 my $migrate_opts = {
2858 migratedfrom
=> $migratedfrom,
2859 spice_ticket
=> $spice_ticket,
2860 network
=> $migration_network,
2861 type
=> $migration_type,
2862 storagemap
=> $storagemap,
2863 nbd_proto_version
=> $nbd_protocol_version,
2864 replicated_volumes
=> $replicated_volumes,
2865 offline_volumes
=> $offline_volumes,
2869 statefile
=> $stateuri,
2870 skiplock
=> $skiplock,
2871 forcemachine
=> $machine,
2872 timeout
=> $timeout,
2873 forcecpu
=> $force_cpu,
2876 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2880 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2884 __PACKAGE__-
>register_method({
2886 path
=> '{vmid}/status/stop',
2890 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2891 "is akin to pulling the power plug of a running computer and may damage the VM data",
2893 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2896 additionalProperties
=> 0,
2898 node
=> get_standard_option
('pve-node'),
2899 vmid
=> get_standard_option
('pve-vmid',
2900 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2901 skiplock
=> get_standard_option
('skiplock'),
2902 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2904 description
=> "Wait maximal timeout seconds.",
2910 description
=> "Do not deactivate storage volumes.",
2923 my $rpcenv = PVE
::RPCEnvironment
::get
();
2924 my $authuser = $rpcenv->get_user();
2926 my $node = extract_param
($param, 'node');
2927 my $vmid = extract_param
($param, 'vmid');
2929 my $skiplock = extract_param
($param, 'skiplock');
2930 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2931 if $skiplock && $authuser ne 'root@pam';
2933 my $keepActive = extract_param
($param, 'keepActive');
2934 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2935 if $keepActive && $authuser ne 'root@pam';
2937 my $migratedfrom = extract_param
($param, 'migratedfrom');
2938 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2939 if $migratedfrom && $authuser ne 'root@pam';
2942 my $storecfg = PVE
::Storage
::config
();
2944 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2949 print "Requesting HA stop for VM $vmid\n";
2951 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2952 PVE
::Tools
::run_command
($cmd);
2956 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2962 syslog
('info', "stop VM $vmid: $upid\n");
2964 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2965 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2969 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2973 __PACKAGE__-
>register_method({
2975 path
=> '{vmid}/status/reset',
2979 description
=> "Reset virtual machine.",
2981 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2984 additionalProperties
=> 0,
2986 node
=> get_standard_option
('pve-node'),
2987 vmid
=> get_standard_option
('pve-vmid',
2988 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2989 skiplock
=> get_standard_option
('skiplock'),
2998 my $rpcenv = PVE
::RPCEnvironment
::get
();
3000 my $authuser = $rpcenv->get_user();
3002 my $node = extract_param
($param, 'node');
3004 my $vmid = extract_param
($param, 'vmid');
3006 my $skiplock = extract_param
($param, 'skiplock');
3007 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3008 if $skiplock && $authuser ne 'root@pam';
3010 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3015 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
3020 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
3023 __PACKAGE__-
>register_method({
3024 name
=> 'vm_shutdown',
3025 path
=> '{vmid}/status/shutdown',
3029 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
3030 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
3032 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3035 additionalProperties
=> 0,
3037 node
=> get_standard_option
('pve-node'),
3038 vmid
=> get_standard_option
('pve-vmid',
3039 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3040 skiplock
=> get_standard_option
('skiplock'),
3042 description
=> "Wait maximal timeout seconds.",
3048 description
=> "Make sure the VM stops.",
3054 description
=> "Do not deactivate storage volumes.",
3067 my $rpcenv = PVE
::RPCEnvironment
::get
();
3068 my $authuser = $rpcenv->get_user();
3070 my $node = extract_param
($param, 'node');
3071 my $vmid = extract_param
($param, 'vmid');
3073 my $skiplock = extract_param
($param, 'skiplock');
3074 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3075 if $skiplock && $authuser ne 'root@pam';
3077 my $keepActive = extract_param
($param, 'keepActive');
3078 raise_param_exc
({ keepActive
=> "Only root may use this option." })
3079 if $keepActive && $authuser ne 'root@pam';
3081 my $storecfg = PVE
::Storage
::config
();
3085 # sending a graceful shutdown command to paused VMs runs into timeouts, and even worse, when
3086 # the VM gets resumed later, it still gets the request delivered and powers off
3087 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
3088 if ($param->{forceStop
}) {
3089 warn "VM is paused - stop instead of shutdown\n";
3092 die "VM is paused - cannot shutdown\n";
3096 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3098 my $timeout = $param->{timeout
} // 60;
3102 print "Requesting HA stop for VM $vmid\n";
3104 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
3105 PVE
::Tools
::run_command
($cmd);
3109 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3116 syslog
('info', "shutdown VM $vmid: $upid\n");
3118 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
3119 $shutdown, $param->{forceStop
}, $keepActive);
3123 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
3127 __PACKAGE__-
>register_method({
3128 name
=> 'vm_reboot',
3129 path
=> '{vmid}/status/reboot',
3133 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
3135 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3138 additionalProperties
=> 0,
3140 node
=> get_standard_option
('pve-node'),
3141 vmid
=> get_standard_option
('pve-vmid',
3142 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3144 description
=> "Wait maximal timeout seconds for the shutdown.",
3157 my $rpcenv = PVE
::RPCEnvironment
::get
();
3158 my $authuser = $rpcenv->get_user();
3160 my $node = extract_param
($param, 'node');
3161 my $vmid = extract_param
($param, 'vmid');
3163 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
3165 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3170 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3171 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3175 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3178 __PACKAGE__-
>register_method({
3179 name
=> 'vm_suspend',
3180 path
=> '{vmid}/status/suspend',
3184 description
=> "Suspend virtual machine.",
3186 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3187 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3188 " on the storage for the vmstate.",
3189 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3192 additionalProperties
=> 0,
3194 node
=> get_standard_option
('pve-node'),
3195 vmid
=> get_standard_option
('pve-vmid',
3196 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3197 skiplock
=> get_standard_option
('skiplock'),
3202 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3204 statestorage
=> get_standard_option
('pve-storage-id', {
3205 description
=> "The storage for the VM state",
3206 requires
=> 'todisk',
3208 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3218 my $rpcenv = PVE
::RPCEnvironment
::get
();
3219 my $authuser = $rpcenv->get_user();
3221 my $node = extract_param
($param, 'node');
3222 my $vmid = extract_param
($param, 'vmid');
3224 my $todisk = extract_param
($param, 'todisk') // 0;
3226 my $statestorage = extract_param
($param, 'statestorage');
3228 my $skiplock = extract_param
($param, 'skiplock');
3229 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3230 if $skiplock && $authuser ne 'root@pam';
3232 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3234 die "Cannot suspend HA managed VM to disk\n"
3235 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3237 # early check for storage permission, for better user feedback
3239 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3240 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3242 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3243 for my $key (keys %$conf) {
3244 next if $key !~ /^hostpci\d+/;
3245 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3246 ." possibility to save/restore their internal state\n";
3249 if (!$statestorage) {
3250 # get statestorage from config if none is given
3251 my $storecfg = PVE
::Storage
::config
();
3252 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3255 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3261 syslog
('info', "suspend VM $vmid: $upid\n");
3263 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3268 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3269 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3272 __PACKAGE__-
>register_method({
3273 name
=> 'vm_resume',
3274 path
=> '{vmid}/status/resume',
3278 description
=> "Resume virtual machine.",
3280 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3283 additionalProperties
=> 0,
3285 node
=> get_standard_option
('pve-node'),
3286 vmid
=> get_standard_option
('pve-vmid',
3287 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3288 skiplock
=> get_standard_option
('skiplock'),
3289 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3299 my $rpcenv = PVE
::RPCEnvironment
::get
();
3301 my $authuser = $rpcenv->get_user();
3303 my $node = extract_param
($param, 'node');
3305 my $vmid = extract_param
($param, 'vmid');
3307 my $skiplock = extract_param
($param, 'skiplock');
3308 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3309 if $skiplock && $authuser ne 'root@pam';
3311 # nocheck is used as part of migration when config file might be still
3313 my $nocheck = extract_param
($param, 'nocheck');
3314 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3315 if $nocheck && $authuser ne 'root@pam';
3317 my $to_disk_suspended;
3319 PVE
::QemuConfig-
>lock_config($vmid, sub {
3320 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3321 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3325 die "VM $vmid not running\n"
3326 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3331 syslog
('info', "resume VM $vmid: $upid\n");
3333 if (!$to_disk_suspended) {
3334 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3336 my $storecfg = PVE
::Storage
::config
();
3337 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3343 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3346 __PACKAGE__-
>register_method({
3347 name
=> 'vm_sendkey',
3348 path
=> '{vmid}/sendkey',
3352 description
=> "Send key event to virtual machine.",
3354 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3357 additionalProperties
=> 0,
3359 node
=> get_standard_option
('pve-node'),
3360 vmid
=> get_standard_option
('pve-vmid',
3361 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3362 skiplock
=> get_standard_option
('skiplock'),
3364 description
=> "The key (qemu monitor encoding).",
3369 returns
=> { type
=> 'null'},
3373 my $rpcenv = PVE
::RPCEnvironment
::get
();
3375 my $authuser = $rpcenv->get_user();
3377 my $node = extract_param
($param, 'node');
3379 my $vmid = extract_param
($param, 'vmid');
3381 my $skiplock = extract_param
($param, 'skiplock');
3382 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3383 if $skiplock && $authuser ne 'root@pam';
3385 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3390 __PACKAGE__-
>register_method({
3391 name
=> 'vm_feature',
3392 path
=> '{vmid}/feature',
3396 description
=> "Check if feature for virtual machine is available.",
3398 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3401 additionalProperties
=> 0,
3403 node
=> get_standard_option
('pve-node'),
3404 vmid
=> get_standard_option
('pve-vmid'),
3406 description
=> "Feature to check.",
3408 enum
=> [ 'snapshot', 'clone', 'copy' ],
3410 snapname
=> get_standard_option
('pve-snapshot-name', {
3418 hasFeature
=> { type
=> 'boolean' },
3421 items
=> { type
=> 'string' },
3428 my $node = extract_param
($param, 'node');
3430 my $vmid = extract_param
($param, 'vmid');
3432 my $snapname = extract_param
($param, 'snapname');
3434 my $feature = extract_param
($param, 'feature');
3436 my $running = PVE
::QemuServer
::check_running
($vmid);
3438 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3441 my $snap = $conf->{snapshots
}->{$snapname};
3442 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3445 my $storecfg = PVE
::Storage
::config
();
3447 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3448 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3451 hasFeature
=> $hasFeature,
3452 nodes
=> [ keys %$nodelist ],
3456 __PACKAGE__-
>register_method({
3458 path
=> '{vmid}/clone',
3462 description
=> "Create a copy of virtual machine/template.",
3464 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3465 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3466 "'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet",
3469 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3471 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3472 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3477 additionalProperties
=> 0,
3479 node
=> get_standard_option
('pve-node'),
3480 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3481 newid
=> get_standard_option
('pve-vmid', {
3482 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3483 description
=> 'VMID for the clone.' }),
3486 type
=> 'string', format
=> 'dns-name',
3487 description
=> "Set a name for the new VM.",
3492 description
=> "Description for the new VM.",
3496 type
=> 'string', format
=> 'pve-poolid',
3497 description
=> "Add the new VM to the specified pool.",
3499 snapname
=> get_standard_option
('pve-snapshot-name', {
3502 storage
=> get_standard_option
('pve-storage-id', {
3503 description
=> "Target storage for full clone.",
3507 description
=> "Target format for file storage. Only valid for full clone.",
3510 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3515 description
=> "Create a full copy of all disks. This is always done when " .
3516 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3518 target
=> get_standard_option
('pve-node', {
3519 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3523 description
=> "Override I/O bandwidth limit (in KiB/s).",
3527 default => 'clone limit from datacenter or storage config',
3537 my $rpcenv = PVE
::RPCEnvironment
::get
();
3538 my $authuser = $rpcenv->get_user();
3540 my $node = extract_param
($param, 'node');
3541 my $vmid = extract_param
($param, 'vmid');
3542 my $newid = extract_param
($param, 'newid');
3543 my $pool = extract_param
($param, 'pool');
3545 my $snapname = extract_param
($param, 'snapname');
3546 my $storage = extract_param
($param, 'storage');
3547 my $format = extract_param
($param, 'format');
3548 my $target = extract_param
($param, 'target');
3550 my $localnode = PVE
::INotify
::nodename
();
3552 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3556 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3558 my $load_and_check = sub {
3559 $rpcenv->check_pool_exist($pool) if defined($pool);
3560 PVE
::Cluster
::check_node_exists
($target) if $target;
3562 my $storecfg = PVE
::Storage
::config
();
3565 # check if storage is enabled on local node
3566 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3568 # check if storage is available on target node
3569 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3570 # clone only works if target storage is shared
3571 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3572 die "can't clone to non-shared storage '$storage'\n"
3573 if !$scfg->{shared
};
3577 PVE
::Cluster
::check_cfs_quorum
();
3579 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3580 PVE
::QemuConfig-
>check_lock($conf);
3582 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3583 die "unexpected state change\n" if $verify_running != $running;
3585 die "snapshot '$snapname' does not exist\n"
3586 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3588 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3590 die "parameter 'storage' not allowed for linked clones\n"
3591 if defined($storage) && !$full;
3593 die "parameter 'format' not allowed for linked clones\n"
3594 if defined($format) && !$full;
3596 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3598 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3599 PVE
::QemuServer
::check_mapping_access
($rpcenv, $authuser, $oldconf);
3601 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $oldconf);
3603 die "can't clone VM to node '$target' (VM uses local storage)\n"
3604 if $target && !$sharedvm;
3606 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3607 die "unable to create VM $newid: config file already exists\n"
3610 my $newconf = { lock => 'clone' };
3615 foreach my $opt (keys %$oldconf) {
3616 my $value = $oldconf->{$opt};
3618 # do not copy snapshot related info
3619 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3620 $opt eq 'vmstate' || $opt eq 'snapstate';
3622 # no need to copy unused images, because VMID(owner) changes anyways
3623 next if $opt =~ m/^unused\d+$/;
3625 die "cannot clone TPM state while VM is running\n"
3626 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3628 # always change MAC! address
3629 if ($opt =~ m/^net(\d+)$/) {
3630 my $net = PVE
::QemuServer
::parse_net
($value);
3631 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3632 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3633 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3634 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3635 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3636 die "unable to parse drive options for '$opt'\n" if !$drive;
3637 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3638 $newconf->{$opt} = $value; # simply copy configuration
3640 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3641 die "Full clone feature is not supported for drive '$opt'\n"
3642 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3643 $fullclone->{$opt} = 1;
3645 # not full means clone instead of copy
3646 die "Linked clone feature is not supported for drive '$opt'\n"
3647 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3649 $drives->{$opt} = $drive;
3650 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3651 push @$vollist, $drive->{file
};
3654 # copy everything else
3655 $newconf->{$opt} = $value;
3659 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3663 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3664 my $storecfg = PVE
::Storage
::config
();
3666 # auto generate a new uuid
3667 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3668 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3669 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3670 # auto generate a new vmgenid only if the option was set for template
3671 if ($newconf->{vmgenid
}) {
3672 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3675 delete $newconf->{template
};
3677 if ($param->{name
}) {
3678 $newconf->{name
} = $param->{name
};
3680 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3683 if ($param->{description
}) {
3684 $newconf->{description
} = $param->{description
};
3687 # create empty/temp config - this fails if VM already exists on other node
3688 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3689 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3691 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3693 my $newvollist = [];
3700 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3702 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3704 my $bwlimit = extract_param
($param, 'bwlimit');
3706 my $total_jobs = scalar(keys %{$drives});
3709 foreach my $opt (sort keys %$drives) {
3710 my $drive = $drives->{$opt};
3711 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3712 my $completion = $skipcomplete ?
'skip' : 'complete';
3714 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3715 my $storage_list = [ $src_sid ];
3716 push @$storage_list, $storage if defined($storage);
3717 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3721 running
=> $running,
3724 snapname
=> $snapname,
3730 storage
=> $storage,
3734 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3735 if $opt eq 'efidisk0';
3737 my $newdrive = PVE
::QemuServer
::clone_disk
(
3749 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3751 PVE
::QemuConfig-
>write_config($newid, $newconf);
3755 delete $newconf->{lock};
3757 # do not write pending changes
3758 if (my @changes = keys %{$newconf->{pending
}}) {
3759 my $pending = join(',', @changes);
3760 warn "found pending changes for '$pending', discarding for clone\n";
3761 delete $newconf->{pending
};
3764 PVE
::QemuConfig-
>write_config($newid, $newconf);
3767 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3768 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3769 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3771 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3772 die "Failed to move config to node '$target' - rename failed: $!\n"
3773 if !rename($conffile, $newconffile);
3776 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3779 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3780 sleep 1; # some storage like rbd need to wait before release volume - really?
3782 foreach my $volid (@$newvollist) {
3783 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3787 PVE
::Firewall
::remove_vmfw_conf
($newid);
3789 unlink $conffile; # avoid races -> last thing before die
3791 die "clone failed: $err";
3797 # Aquire exclusive lock lock for $newid
3798 my $lock_target_vm = sub {
3799 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3802 my $lock_source_vm = sub {
3803 # exclusive lock if VM is running - else shared lock is enough;
3805 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3807 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3811 $load_and_check->(); # early checks before forking/locking
3813 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3816 __PACKAGE__-
>register_method({
3817 name
=> 'move_vm_disk',
3818 path
=> '{vmid}/move_disk',
3822 description
=> "Move volume to different storage or to a different VM.",
3824 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3825 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3826 "a disk to another VM, you need the permissions on the target VM as well.",
3827 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3830 additionalProperties
=> 0,
3832 node
=> get_standard_option
('pve-node'),
3833 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3834 'target-vmid' => get_standard_option
('pve-vmid', {
3835 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3840 description
=> "The disk you want to move.",
3841 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3843 storage
=> get_standard_option
('pve-storage-id', {
3844 description
=> "Target storage.",
3845 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3850 description
=> "Target Format.",
3851 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3856 description
=> "Delete the original disk after successful copy. By default the"
3857 ." original disk is kept as unused disk.",
3863 description
=> 'Prevent changes if current configuration file has different SHA1"
3864 ." digest. This can be used to prevent concurrent modifications.',
3869 description
=> "Override I/O bandwidth limit (in KiB/s).",
3873 default => 'move limit from datacenter or storage config',
3877 description
=> "The config key the disk will be moved to on the target VM"
3878 ." (for example, ide0 or scsi1). Default is the source disk key.",
3879 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3882 'target-digest' => {
3884 description
=> 'Prevent changes if the current config file of the target VM has a"
3885 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3893 description
=> "the task ID.",
3898 my $rpcenv = PVE
::RPCEnvironment
::get
();
3899 my $authuser = $rpcenv->get_user();
3901 my $node = extract_param
($param, 'node');
3902 my $vmid = extract_param
($param, 'vmid');
3903 my $target_vmid = extract_param
($param, 'target-vmid');
3904 my $digest = extract_param
($param, 'digest');
3905 my $target_digest = extract_param
($param, 'target-digest');
3906 my $disk = extract_param
($param, 'disk');
3907 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3908 my $storeid = extract_param
($param, 'storage');
3909 my $format = extract_param
($param, 'format');
3911 my $storecfg = PVE
::Storage
::config
();
3913 my $load_and_check_move = sub {
3914 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3915 PVE
::QemuConfig-
>check_lock($conf);
3917 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3919 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3921 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3923 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3924 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3926 my $old_volid = $drive->{file
};
3928 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3929 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3933 die "you can't move to the same storage with same format\n"
3934 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3936 # this only checks snapshots because $disk is passed!
3937 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3943 die "you can't move a disk with snapshots and delete the source\n"
3944 if $snapshotted && $param->{delete};
3946 return ($conf, $drive, $oldstoreid, $snapshotted);
3949 my $move_updatefn = sub {
3950 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3951 my $old_volid = $drive->{file
};
3953 PVE
::Cluster
::log_msg
(
3956 "move disk VM $vmid: move --disk $disk --storage $storeid"
3959 my $running = PVE
::QemuServer
::check_running
($vmid);
3961 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3963 my $newvollist = [];
3969 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3971 warn "moving disk with snapshots, snapshots will not be moved!\n"
3974 my $bwlimit = extract_param
($param, 'bwlimit');
3975 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3977 [$oldstoreid, $storeid],
3983 running
=> $running,
3992 storage
=> $storeid,
3996 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3997 if $disk eq 'efidisk0';
3999 my $newdrive = PVE
::QemuServer
::clone_disk
(
4010 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
4012 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
4014 # convert moved disk to base if part of template
4015 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
4016 if PVE
::QemuConfig-
>is_template($conf);
4018 PVE
::QemuConfig-
>write_config($vmid, $conf);
4020 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
4021 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
4022 eval { mon_cmd
($vmid, "guest-fstrim") };
4026 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
4027 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
4033 foreach my $volid (@$newvollist) {
4034 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
4037 die "storage migration failed: $err";
4040 if ($param->{delete}) {
4042 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
4043 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
4049 my $load_and_check_reassign_configs = sub {
4050 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
4052 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
4053 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
4055 my $source_node = $vmlist->{$vmid}->{node
};
4056 my $target_node = $vmlist->{$target_vmid}->{node
};
4058 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
4059 if $source_node ne $target_node;
4061 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
4062 PVE
::QemuConfig-
>check_lock($source_conf);
4063 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
4064 PVE
::QemuConfig-
>check_lock($target_conf);
4066 die "Can't move disks from or to template VMs\n"
4067 if ($source_conf->{template
} || $target_conf->{template
});
4070 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
4071 die "VM ${vmid}: $@" if $@;
4074 if ($target_digest) {
4075 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
4076 die "VM ${target_vmid}: $@" if $@;
4079 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
4081 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
4082 if $target_conf->{$target_disk};
4084 my $drive = PVE
::QemuServer
::parse_drive
(
4086 $source_conf->{$disk},
4088 die "failed to parse source disk - $@\n" if !$drive;
4090 my $source_volid = $drive->{file
};
4092 die "disk '${disk}' has no associated volume\n" if !$source_volid;
4093 die "CD drive contents can't be moved to another VM\n"
4094 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
4096 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
4097 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
4099 die "Can't move disk used by a snapshot to another VM\n"
4100 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
4101 die "Storage does not support moving of this disk to another VM\n"
4102 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
4103 die "Cannot move disk to another VM while the source VM is running - detach first\n"
4104 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
4106 # now re-parse using target disk slot format
4107 if ($target_disk =~ /^unused\d+$/) {
4108 $drive = PVE
::QemuServer
::parse_drive
(
4113 $drive = PVE
::QemuServer
::parse_drive
(
4115 $source_conf->{$disk},
4118 die "failed to parse source disk for target disk format - $@\n" if !$drive;
4120 my $repl_conf = PVE
::ReplicationConfig-
>new();
4121 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
4122 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4123 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
4124 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
4127 return ($source_conf, $target_conf, $drive);
4132 print STDERR
"$msg\n";
4135 my $disk_reassignfn = sub {
4136 return PVE
::QemuConfig-
>lock_config($vmid, sub {
4137 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
4138 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
4140 my $source_volid = $drive->{file
};
4142 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
4143 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
4145 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4147 my $new_volid = PVE
::Storage
::rename_volume
(
4153 $drive->{file
} = $new_volid;
4155 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
4156 if (defined(delete $boot_order->{$disk})) {
4157 print "removing disk '$disk' from boot order config\n";
4158 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
4159 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
4162 delete $source_conf->{$disk};
4163 print "removing disk '${disk}' from VM '${vmid}' config\n";
4164 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
4166 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
4168 if ($target_disk =~ /^unused\d+$/) {
4169 $target_conf->{$target_disk} = $drive_string;
4170 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4175 vmid
=> $target_vmid,
4176 digest
=> $target_digest,
4177 $target_disk => $drive_string,
4183 # remove possible replication snapshots
4184 if (PVE
::Storage
::volume_has_feature
(
4190 PVE
::Replication
::prepare
(
4200 print "Failed to remove replication snapshots on moved disk " .
4201 "'$target_disk'. Manual cleanup could be necessary.\n";
4208 if ($target_vmid && $storeid) {
4209 my $msg = "either set 'storage' or 'target-vmid', but not both";
4210 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4211 } elsif ($target_vmid) {
4212 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4213 if $authuser ne 'root@pam';
4215 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4216 if $vmid eq $target_vmid;
4218 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4219 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4220 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4222 return $rpcenv->fork_worker(
4224 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4228 } elsif ($storeid) {
4229 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4231 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4232 if $disk =~ m/^unused\d+$/;
4234 $load_and_check_move->(); # early checks before forking/locking
4237 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4240 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4242 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4243 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4247 my $check_vm_disks_local = sub {
4248 my ($storecfg, $vmconf, $vmid) = @_;
4250 my $local_disks = {};
4252 # add some more information to the disks e.g. cdrom
4253 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4254 my ($volid, $attr) = @_;
4256 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4258 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4259 return if $scfg->{shared
};
4261 # The shared attr here is just a special case where the vdisk
4262 # is marked as shared manually
4263 return if $attr->{shared
};
4264 return if $attr->{cdrom
} and $volid eq "none";
4266 if (exists $local_disks->{$volid}) {
4267 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4269 $local_disks->{$volid} = $attr;
4270 # ensure volid is present in case it's needed
4271 $local_disks->{$volid}->{volid
} = $volid;
4275 return $local_disks;
4278 __PACKAGE__-
>register_method({
4279 name
=> 'migrate_vm_precondition',
4280 path
=> '{vmid}/migrate',
4284 description
=> "Get preconditions for migration.",
4286 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4289 additionalProperties
=> 0,
4291 node
=> get_standard_option
('pve-node'),
4292 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4293 target
=> get_standard_option
('pve-node', {
4294 description
=> "Target node.",
4295 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4303 running
=> { type
=> 'boolean' },
4307 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4309 not_allowed_nodes
=> {
4312 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4316 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4318 local_resources
=> {
4320 description
=> "List local resources e.g. pci, usb"
4322 'mapped-resources' => {
4324 description
=> "List of mapped resources e.g. pci, usb"
4331 my $rpcenv = PVE
::RPCEnvironment
::get
();
4333 my $authuser = $rpcenv->get_user();
4335 PVE
::Cluster
::check_cfs_quorum
();
4339 my $vmid = extract_param
($param, 'vmid');
4340 my $target = extract_param
($param, 'target');
4341 my $localnode = PVE
::INotify
::nodename
();
4345 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4346 my $storecfg = PVE
::Storage
::config
();
4349 # try to detect errors early
4350 PVE
::QemuConfig-
>check_lock($vmconf);
4352 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4354 my ($local_resources, $mapped_resources, $missing_mappings_by_node) =
4355 PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4356 delete $missing_mappings_by_node->{$localnode};
4358 # if vm is not running, return target nodes where local storage/mapped devices are available
4359 # for offline migration
4360 if (!$res->{running
}) {
4361 $res->{allowed_nodes
} = [];
4362 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4363 delete $checked_nodes->{$localnode};
4365 foreach my $node (keys %$checked_nodes) {
4366 my $missing_mappings = $missing_mappings_by_node->{$node};
4367 if (scalar($missing_mappings->@*)) {
4368 $checked_nodes->{$node}->{'unavailable-resources'} = $missing_mappings;
4372 if (!defined($checked_nodes->{$node}->{unavailable_storages
})) {
4373 push @{$res->{allowed_nodes
}}, $node;
4377 $res->{not_allowed_nodes
} = $checked_nodes;
4380 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4381 $res->{local_disks
} = [ values %$local_disks ];;
4383 $res->{local_resources
} = $local_resources;
4384 $res->{'mapped-resources'} = $mapped_resources;
4391 __PACKAGE__-
>register_method({
4392 name
=> 'migrate_vm',
4393 path
=> '{vmid}/migrate',
4397 description
=> "Migrate virtual machine. Creates a new migration task.",
4399 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4402 additionalProperties
=> 0,
4404 node
=> get_standard_option
('pve-node'),
4405 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4406 target
=> get_standard_option
('pve-node', {
4407 description
=> "Target node.",
4408 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4412 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4417 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4422 enum
=> ['secure', 'insecure'],
4423 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4426 migration_network
=> {
4427 type
=> 'string', format
=> 'CIDR',
4428 description
=> "CIDR of the (sub) network that is used for migration.",
4431 "with-local-disks" => {
4433 description
=> "Enable live storage migration for local disk",
4436 targetstorage
=> get_standard_option
('pve-targetstorage', {
4437 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4440 description
=> "Override I/O bandwidth limit (in KiB/s).",
4444 default => 'migrate limit from datacenter or storage config',
4450 description
=> "the task ID.",
4455 my $rpcenv = PVE
::RPCEnvironment
::get
();
4456 my $authuser = $rpcenv->get_user();
4458 my $target = extract_param
($param, 'target');
4460 my $localnode = PVE
::INotify
::nodename
();
4461 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4463 PVE
::Cluster
::check_cfs_quorum
();
4465 PVE
::Cluster
::check_node_exists
($target);
4467 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4469 my $vmid = extract_param
($param, 'vmid');
4471 raise_param_exc
({ force
=> "Only root may use this option." })
4472 if $param->{force
} && $authuser ne 'root@pam';
4474 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4475 if $param->{migration_type
} && $authuser ne 'root@pam';
4477 # allow root only until better network permissions are available
4478 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4479 if $param->{migration_network
} && $authuser ne 'root@pam';
4482 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4484 # try to detect errors early
4486 PVE
::QemuConfig-
>check_lock($conf);
4488 if (PVE
::QemuServer
::check_running
($vmid)) {
4489 die "can't migrate running VM without --online\n" if !$param->{online
};
4491 my $repl_conf = PVE
::ReplicationConfig-
>new();
4492 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4493 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4494 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4495 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4496 "target. Use 'force' to override.\n";
4499 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4500 $param->{online
} = 0;
4503 my $storecfg = PVE
::Storage
::config
();
4504 if (my $targetstorage = $param->{targetstorage
}) {
4505 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4506 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4509 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4510 if !defined($storagemap->{identity
});
4512 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4513 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4516 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4517 if $storagemap->{default};
4519 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4520 if $storagemap->{identity
};
4522 $param->{storagemap
} = $storagemap;
4524 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4527 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4532 print "Requesting HA migration for VM $vmid to node $target\n";
4534 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4535 PVE
::Tools
::run_command
($cmd);
4539 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4544 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4548 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4551 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4556 __PACKAGE__-
>register_method({
4557 name
=> 'remote_migrate_vm',
4558 path
=> '{vmid}/remote_migrate',
4562 description
=> "Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!",
4564 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4567 additionalProperties
=> 0,
4569 node
=> get_standard_option
('pve-node'),
4570 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4571 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
4572 'target-endpoint' => get_standard_option
('proxmox-remote', {
4573 description
=> "Remote target endpoint",
4577 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4582 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.",
4586 'target-storage' => get_standard_option
('pve-targetstorage', {
4587 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4590 'target-bridge' => {
4592 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.",
4593 format
=> 'bridge-pair-list',
4596 description
=> "Override I/O bandwidth limit (in KiB/s).",
4600 default => 'migrate limit from datacenter or storage config',
4606 description
=> "the task ID.",
4611 my $rpcenv = PVE
::RPCEnvironment
::get
();
4612 my $authuser = $rpcenv->get_user();
4614 my $source_vmid = extract_param
($param, 'vmid');
4615 my $target_endpoint = extract_param
($param, 'target-endpoint');
4616 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
4618 my $delete = extract_param
($param, 'delete') // 0;
4620 PVE
::Cluster
::check_cfs_quorum
();
4623 my $conf = PVE
::QemuConfig-
>load_config($source_vmid);
4625 PVE
::QemuConfig-
>check_lock($conf);
4627 raise_param_exc
({ vmid
=> "cannot migrate HA-managed VM to remote cluster" })
4628 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
4630 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
4632 # TODO: move this as helper somewhere appropriate?
4634 protocol
=> 'https',
4635 host
=> $remote->{host
},
4636 port
=> $remote->{port
} // 8006,
4637 apitoken
=> $remote->{apitoken
},
4641 if ($fp = $remote->{fingerprint
}) {
4642 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
4645 print "Establishing API connection with remote at '$remote->{host}'\n";
4647 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
4649 if (!defined($fp)) {
4650 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
4651 foreach my $cert (@$cert_info) {
4652 my $filename = $cert->{filename
};
4653 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
4654 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
4656 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
4660 my $repl_conf = PVE
::ReplicationConfig-
>new();
4661 my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1);
4662 die "cannot remote-migrate replicated VM\n" if $is_replicated;
4664 if (PVE
::QemuServer
::check_running
($source_vmid)) {
4665 die "can't migrate running VM without --online\n" if !$param->{online
};
4668 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4669 $param->{online
} = 0;
4672 my $storecfg = PVE
::Storage
::config
();
4673 my $target_storage = extract_param
($param, 'target-storage');
4674 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
4675 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
4678 my $target_bridge = extract_param
($param, 'target-bridge');
4679 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
4680 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
4683 die "remote migration requires explicit storage mapping!\n"
4684 if $storagemap->{identity
};
4686 $param->{storagemap
} = $storagemap;
4687 $param->{bridgemap
} = $bridgemap;
4688 $param->{remote
} = {
4689 conn
=> $conn_args, # re-use fingerprint for tunnel
4690 client
=> $api_client,
4691 vmid
=> $target_vmid,
4693 $param->{migration_type
} = 'websocket';
4694 $param->{'with-local-disks'} = 1;
4695 $param->{delete} = $delete if $delete;
4697 my $cluster_status = $api_client->get("/cluster/status");
4699 foreach my $entry (@$cluster_status) {
4700 next if $entry->{type
} ne 'node';
4701 if ($entry->{local}) {
4702 $target_node = $entry->{name
};
4707 die "couldn't determine endpoint's node name\n"
4708 if !defined($target_node);
4711 PVE
::QemuMigrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
4715 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
4718 return $rpcenv->fork_worker('qmigrate', $source_vmid, $authuser, $worker);
4721 __PACKAGE__-
>register_method({
4723 path
=> '{vmid}/monitor',
4727 description
=> "Execute QEMU monitor commands.",
4729 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4730 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4733 additionalProperties
=> 0,
4735 node
=> get_standard_option
('pve-node'),
4736 vmid
=> get_standard_option
('pve-vmid'),
4739 description
=> "The monitor command.",
4743 returns
=> { type
=> 'string'},
4747 my $rpcenv = PVE
::RPCEnvironment
::get
();
4748 my $authuser = $rpcenv->get_user();
4751 my $command = shift;
4752 return $command =~ m/^\s*info(\s+|$)/
4753 || $command =~ m/^\s*help\s*$/;
4756 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4757 if !&$is_ro($param->{command
});
4759 my $vmid = $param->{vmid
};
4761 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4765 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4767 $res = "ERROR: $@" if $@;
4772 __PACKAGE__-
>register_method({
4773 name
=> 'resize_vm',
4774 path
=> '{vmid}/resize',
4778 description
=> "Extend volume size.",
4780 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4783 additionalProperties
=> 0,
4785 node
=> get_standard_option
('pve-node'),
4786 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4787 skiplock
=> get_standard_option
('skiplock'),
4790 description
=> "The disk you want to resize.",
4791 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4795 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4796 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.",
4800 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4808 description
=> "the task ID.",
4813 my $rpcenv = PVE
::RPCEnvironment
::get
();
4815 my $authuser = $rpcenv->get_user();
4817 my $node = extract_param
($param, 'node');
4819 my $vmid = extract_param
($param, 'vmid');
4821 my $digest = extract_param
($param, 'digest');
4823 my $disk = extract_param
($param, 'disk');
4825 my $sizestr = extract_param
($param, 'size');
4827 my $skiplock = extract_param
($param, 'skiplock');
4828 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4829 if $skiplock && $authuser ne 'root@pam';
4831 my $storecfg = PVE
::Storage
::config
();
4833 my $updatefn = sub {
4835 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4837 die "checksum missmatch (file change by other user?)\n"
4838 if $digest && $digest ne $conf->{digest
};
4839 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4841 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4843 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4845 my (undef, undef, undef, undef, undef, undef, $format) =
4846 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4848 my $volid = $drive->{file
};
4850 die "disk '$disk' has no associated volume\n" if !$volid;
4852 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4854 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4856 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4858 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4859 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4861 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4863 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4864 my ($ext, $newsize, $unit) = ($1, $2, $4);
4867 $newsize = $newsize * 1024;
4868 } elsif ($unit eq 'M') {
4869 $newsize = $newsize * 1024 * 1024;
4870 } elsif ($unit eq 'G') {
4871 $newsize = $newsize * 1024 * 1024 * 1024;
4872 } elsif ($unit eq 'T') {
4873 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4876 $newsize += $size if $ext;
4877 $newsize = int($newsize);
4879 die "shrinking disks is not supported\n" if $newsize < $size;
4881 return if $size == $newsize;
4883 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4885 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4887 $drive->{size
} = $newsize;
4888 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4890 PVE
::QemuConfig-
>write_config($vmid, $conf);
4894 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4897 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
4900 __PACKAGE__-
>register_method({
4901 name
=> 'snapshot_list',
4902 path
=> '{vmid}/snapshot',
4904 description
=> "List all snapshots.",
4906 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4909 protected
=> 1, # qemu pid files are only readable by root
4911 additionalProperties
=> 0,
4913 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4914 node
=> get_standard_option
('pve-node'),
4923 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4927 description
=> "Snapshot includes RAM.",
4932 description
=> "Snapshot description.",
4936 description
=> "Snapshot creation time",
4938 renderer
=> 'timestamp',
4942 description
=> "Parent snapshot identifier.",
4948 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4953 my $vmid = $param->{vmid
};
4955 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4956 my $snaphash = $conf->{snapshots
} || {};
4960 foreach my $name (keys %$snaphash) {
4961 my $d = $snaphash->{$name};
4964 snaptime
=> $d->{snaptime
} || 0,
4965 vmstate
=> $d->{vmstate
} ?
1 : 0,
4966 description
=> $d->{description
} || '',
4968 $item->{parent
} = $d->{parent
} if $d->{parent
};
4969 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4973 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4976 digest
=> $conf->{digest
},
4977 running
=> $running,
4978 description
=> "You are here!",
4980 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4982 push @$res, $current;
4987 __PACKAGE__-
>register_method({
4989 path
=> '{vmid}/snapshot',
4993 description
=> "Snapshot a VM.",
4995 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4998 additionalProperties
=> 0,
5000 node
=> get_standard_option
('pve-node'),
5001 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5002 snapname
=> get_standard_option
('pve-snapshot-name'),
5006 description
=> "Save the vmstate",
5011 description
=> "A textual description or comment.",
5017 description
=> "the task ID.",
5022 my $rpcenv = PVE
::RPCEnvironment
::get
();
5024 my $authuser = $rpcenv->get_user();
5026 my $node = extract_param
($param, 'node');
5028 my $vmid = extract_param
($param, 'vmid');
5030 my $snapname = extract_param
($param, 'snapname');
5032 die "unable to use snapshot name 'current' (reserved name)\n"
5033 if $snapname eq 'current';
5035 die "unable to use snapshot name 'pending' (reserved name)\n"
5036 if lc($snapname) eq 'pending';
5039 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
5040 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
5041 $param->{description
});
5044 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
5047 __PACKAGE__-
>register_method({
5048 name
=> 'snapshot_cmd_idx',
5049 path
=> '{vmid}/snapshot/{snapname}',
5056 additionalProperties
=> 0,
5058 vmid
=> get_standard_option
('pve-vmid'),
5059 node
=> get_standard_option
('pve-node'),
5060 snapname
=> get_standard_option
('pve-snapshot-name'),
5069 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
5076 push @$res, { cmd
=> 'rollback' };
5077 push @$res, { cmd
=> 'config' };
5082 __PACKAGE__-
>register_method({
5083 name
=> 'update_snapshot_config',
5084 path
=> '{vmid}/snapshot/{snapname}/config',
5088 description
=> "Update snapshot metadata.",
5090 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5093 additionalProperties
=> 0,
5095 node
=> get_standard_option
('pve-node'),
5096 vmid
=> get_standard_option
('pve-vmid'),
5097 snapname
=> get_standard_option
('pve-snapshot-name'),
5101 description
=> "A textual description or comment.",
5105 returns
=> { type
=> 'null' },
5109 my $rpcenv = PVE
::RPCEnvironment
::get
();
5111 my $authuser = $rpcenv->get_user();
5113 my $vmid = extract_param
($param, 'vmid');
5115 my $snapname = extract_param
($param, 'snapname');
5117 return if !defined($param->{description
});
5119 my $updatefn = sub {
5121 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5123 PVE
::QemuConfig-
>check_lock($conf);
5125 my $snap = $conf->{snapshots
}->{$snapname};
5127 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5129 $snap->{description
} = $param->{description
} if defined($param->{description
});
5131 PVE
::QemuConfig-
>write_config($vmid, $conf);
5134 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
5139 __PACKAGE__-
>register_method({
5140 name
=> 'get_snapshot_config',
5141 path
=> '{vmid}/snapshot/{snapname}/config',
5144 description
=> "Get snapshot configuration",
5146 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
5149 additionalProperties
=> 0,
5151 node
=> get_standard_option
('pve-node'),
5152 vmid
=> get_standard_option
('pve-vmid'),
5153 snapname
=> get_standard_option
('pve-snapshot-name'),
5156 returns
=> { type
=> "object" },
5160 my $rpcenv = PVE
::RPCEnvironment
::get
();
5162 my $authuser = $rpcenv->get_user();
5164 my $vmid = extract_param
($param, 'vmid');
5166 my $snapname = extract_param
($param, 'snapname');
5168 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5170 my $snap = $conf->{snapshots
}->{$snapname};
5172 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5177 __PACKAGE__-
>register_method({
5179 path
=> '{vmid}/snapshot/{snapname}/rollback',
5183 description
=> "Rollback VM state to specified snapshot.",
5185 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
5188 additionalProperties
=> 0,
5190 node
=> get_standard_option
('pve-node'),
5191 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5192 snapname
=> get_standard_option
('pve-snapshot-name'),
5195 description
=> "Whether the VM should get started after rolling back successfully."
5196 . " (Note: VMs will be automatically started if the snapshot includes RAM.)",
5204 description
=> "the task ID.",
5209 my $rpcenv = PVE
::RPCEnvironment
::get
();
5211 my $authuser = $rpcenv->get_user();
5213 my $node = extract_param
($param, 'node');
5215 my $vmid = extract_param
($param, 'vmid');
5217 my $snapname = extract_param
($param, 'snapname');
5220 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
5221 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
5223 if ($param->{start
} && !PVE
::QemuServer
::Helpers
::vm_running_locally
($vmid)) {
5224 PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node });
5229 # hold migration lock, this makes sure that nobody create replication snapshots
5230 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
5233 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
5236 __PACKAGE__-
>register_method({
5237 name
=> 'delsnapshot',
5238 path
=> '{vmid}/snapshot/{snapname}',
5242 description
=> "Delete a VM snapshot.",
5244 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5247 additionalProperties
=> 0,
5249 node
=> get_standard_option
('pve-node'),
5250 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5251 snapname
=> get_standard_option
('pve-snapshot-name'),
5255 description
=> "For removal from config file, even if removing disk snapshots fails.",
5261 description
=> "the task ID.",
5266 my $rpcenv = PVE
::RPCEnvironment
::get
();
5268 my $authuser = $rpcenv->get_user();
5270 my $node = extract_param
($param, 'node');
5272 my $vmid = extract_param
($param, 'vmid');
5274 my $snapname = extract_param
($param, 'snapname');
5277 my $do_delete = sub {
5279 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
5280 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
5284 if ($param->{force
}) {
5287 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
5289 die $err if $lock_obtained;
5290 die "Failed to obtain guest migration lock - replication running?\n";
5295 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
5298 __PACKAGE__-
>register_method({
5300 path
=> '{vmid}/template',
5304 description
=> "Create a Template.",
5306 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
5307 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
5310 additionalProperties
=> 0,
5312 node
=> get_standard_option
('pve-node'),
5313 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
5317 description
=> "If you want to convert only 1 disk to base image.",
5318 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
5325 description
=> "the task ID.",
5330 my $rpcenv = PVE
::RPCEnvironment
::get
();
5332 my $authuser = $rpcenv->get_user();
5334 my $node = extract_param
($param, 'node');
5336 my $vmid = extract_param
($param, 'vmid');
5338 my $disk = extract_param
($param, 'disk');
5340 my $load_and_check = sub {
5341 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5343 PVE
::QemuConfig-
>check_lock($conf);
5345 die "unable to create template, because VM contains snapshots\n"
5346 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
5348 die "you can't convert a template to a template\n"
5349 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
5351 die "you can't convert a VM to template if VM is running\n"
5352 if PVE
::QemuServer
::check_running
($vmid);
5357 $load_and_check->();
5360 PVE
::QemuConfig-
>lock_config($vmid, sub {
5361 my $conf = $load_and_check->();
5363 $conf->{template
} = 1;
5364 PVE
::QemuConfig-
>write_config($vmid, $conf);
5366 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5370 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5373 __PACKAGE__-
>register_method({
5374 name
=> 'cloudinit_generated_config_dump',
5375 path
=> '{vmid}/cloudinit/dump',
5378 description
=> "Get automatically generated cloudinit config.",
5380 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5383 additionalProperties
=> 0,
5385 node
=> get_standard_option
('pve-node'),
5386 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5388 description
=> 'Config type.',
5390 enum
=> ['user', 'network', 'meta'],
5400 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5402 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});
5405 __PACKAGE__-
>register_method({
5407 path
=> '{vmid}/mtunnel',
5410 description
=> 'Migration tunnel endpoint - only for internal use by VM migration.',
5414 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
5415 ['perm', '/', [ 'Sys.Incoming' ]],
5417 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
5418 " on '/'. Further permission checks happen during the actual migration.",
5421 additionalProperties
=> 0,
5423 node
=> get_standard_option
('pve-node'),
5424 vmid
=> get_standard_option
('pve-vmid'),
5427 format
=> 'pve-storage-id-list',
5429 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
5433 format
=> 'pve-bridge-id-list',
5435 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
5440 additionalProperties
=> 0,
5442 upid
=> { type
=> 'string' },
5443 ticket
=> { type
=> 'string' },
5444 socket => { type
=> 'string' },
5450 my $rpcenv = PVE
::RPCEnvironment
::get
();
5451 my $authuser = $rpcenv->get_user();
5453 my $node = extract_param
($param, 'node');
5454 my $vmid = extract_param
($param, 'vmid');
5456 my $storages = extract_param
($param, 'storages');
5457 my $bridges = extract_param
($param, 'bridges');
5459 my $nodename = PVE
::INotify
::nodename
();
5461 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5462 if $node ne 'localhost' && $node ne $nodename;
5466 my $storecfg = PVE
::Storage
::config
();
5467 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
5468 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
5471 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
5472 PVE
::Network
::read_bridge_mtu
($bridge);
5475 PVE
::Cluster
::check_cfs_quorum
();
5477 my $lock = 'create';
5478 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, 0, $lock); };
5480 raise_param_exc
({ vmid
=> "unable to create empty VM config - $@"})
5485 storecfg
=> PVE
::Storage
::config
(),
5490 my $run_locked = sub {
5491 my ($code, $params) = @_;
5492 return PVE
::QemuConfig-
>lock_config($state->{vmid
}, sub {
5493 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5495 $state->{conf
} = $conf;
5497 die "Encountered wrong lock - aborting mtunnel command handling.\n"
5498 if $state->{lock} && !PVE
::QemuConfig-
>has_lock($conf, $state->{lock});
5500 return $code->($params);
5508 description
=> 'Full VM config, adapted for target cluster/node',
5510 'firewall-config' => {
5512 description
=> 'VM firewall config',
5517 format
=> PVE
::JSONSchema
::get_standard_option
('pve-qm-image-format'),
5520 format
=> 'pve-storage-id',
5524 description
=> 'parsed drive information without volid and format',
5530 description
=> 'params passed to vm_start_nolock',
5534 description
=> 'migrate_opts passed to vm_start_nolock',
5540 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
5546 description
=> 'remove VM config and disks, aborting migration',
5550 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
5551 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
5552 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
5555 my $cmd_handlers = {
5557 # compared against other end's version
5558 # bump/reset for breaking changes
5559 # bump/bump for opt-in changes
5561 api
=> $PVE::QemuMigrate
::WS_TUNNEL_VERSION
,
5568 # parse and write out VM FW config if given
5569 if (my $fw_conf = $params->{'firewall-config'}) {
5570 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
5577 ipset_comments
=> {},
5579 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
5581 # TODO: add flag for strict parsing?
5582 # TODO: add import sub that does all this given raw content?
5583 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
5584 $vmfw_conf->{vmid
} = $state->{vmid
};
5585 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
5587 $state->{cleanup
}->{fw
} = 1;
5590 my $conf_fn = "incoming/qemu-server/$state->{vmid}.conf";
5591 my $new_conf = PVE
::QemuServer
::parse_vm_config
($conf_fn, $params->{conf
}, 1);
5592 delete $new_conf->{lock};
5593 delete $new_conf->{digest
};
5595 # TODO handle properly?
5596 delete $new_conf->{snapshots
};
5597 delete $new_conf->{parent
};
5598 delete $new_conf->{pending
};
5600 # not handled by update_vm_api
5601 my $vmgenid = delete $new_conf->{vmgenid
};
5602 my $meta = delete $new_conf->{meta
};
5603 my $cloudinit = delete $new_conf->{cloudinit
}; # this is informational only
5604 $new_conf->{skip_cloud_init
} = 1; # re-use image from source side
5606 $new_conf->{vmid
} = $state->{vmid
};
5607 $new_conf->{node
} = $node;
5609 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, 'create');
5612 $update_vm_api->($new_conf, 1);
5615 # revert to locked previous config
5616 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5617 $conf->{lock} = 'create';
5618 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5623 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5624 $conf->{lock} = 'migrate';
5625 $conf->{vmgenid
} = $vmgenid if defined($vmgenid);
5626 $conf->{meta
} = $meta if defined($meta);
5627 $conf->{cloudinit
} = $cloudinit if defined($cloudinit);
5628 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5630 $state->{lock} = 'migrate';
5636 return PVE
::StorageTunnel
::handle_bwlimit
($params);
5641 my $format = $params->{format
};
5642 my $storeid = $params->{storage
};
5643 my $drive = $params->{drive
};
5645 $check_storage_access_migrate->($rpcenv, $authuser, $state->{storecfg
}, $storeid, $node);
5648 default => $storeid,
5651 my $source_volumes = {
5661 my $res = PVE
::QemuServer
::vm_migrate_alloc_nbd_disks
($state->{storecfg
}, $state->{vmid
}, $source_volumes, $storagemap);
5662 if (defined($res->{disk
})) {
5663 $state->{cleanup
}->{volumes
}->{$res->{disk
}->{volid
}} = 1;
5664 return $res->{disk
};
5666 die "failed to allocate NBD disk..\n";
5669 'disk-import' => sub {
5672 $check_storage_access_migrate->(
5680 $params->{unix
} = "/run/qemu-server/$state->{vmid}.storage";
5682 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
5684 'query-disk-import' => sub {
5687 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
5692 my $info = PVE
::QemuServer
::vm_start_nolock
(
5696 $params->{start_params
},
5697 $params->{migrate_opts
},
5701 if ($info->{migrate
}->{proto
} ne 'unix') {
5702 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5703 die "migration over non-UNIX sockets not possible\n";
5706 my $socket = $info->{migrate
}->{addr
};
5707 chown $state->{socket_uid
}, -1, $socket;
5708 $state->{sockets
}->{$socket} = 1;
5710 my $unix_sockets = $info->{migrate
}->{unix_sockets
};
5711 foreach my $socket (@$unix_sockets) {
5712 chown $state->{socket_uid
}, -1, $socket;
5713 $state->{sockets
}->{$socket} = 1;
5718 if (PVE
::QemuServer
::qga_check_running
($state->{vmid
})) {
5719 eval { mon_cmd
($state->{vmid
}, "guest-fstrim") };
5720 warn "fstrim failed: $@\n" if $@;
5725 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5729 PVE
::QemuServer
::nbd_stop
($state->{vmid
});
5733 if (PVE
::QemuServer
::Helpers
::vm_running_locally
($state->{vmid
})) {
5734 PVE
::QemuServer
::vm_resume
($state->{vmid
}, 1, 1);
5736 die "VM $state->{vmid} not running\n";
5741 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, $state->{lock});
5742 delete $state->{lock};
5748 my $path = $params->{path
};
5750 die "Not allowed to generate ticket for unknown socket '$path'\n"
5751 if !defined($state->{sockets
}->{$path});
5753 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
5758 if ($params->{cleanup
}) {
5759 if ($state->{cleanup
}->{fw
}) {
5760 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
5763 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
5764 print "freeing volume '$volid' as part of cleanup\n";
5765 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
5769 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5772 print "switching to exit-mode, waiting for client to disconnect\n";
5779 my $socket_addr = "/run/qemu-server/$state->{vmid}.mtunnel";
5780 unlink $socket_addr;
5782 $state->{socket} = IO
::Socket
::UNIX-
>new(
5783 Type
=> SOCK_STREAM
(),
5784 Local
=> $socket_addr,
5788 $state->{socket_uid
} = getpwnam('www-data')
5789 or die "Failed to resolve user 'www-data' to numeric UID\n";
5790 chown $state->{socket_uid
}, -1, $socket_addr;
5793 print "mtunnel started\n";
5795 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
5797 warn "Failed to accept tunnel connection - $@\n";
5799 warn "Removing tunnel socket..\n";
5800 unlink $state->{socket};
5802 warn "Removing temporary VM config..\n";
5804 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5807 die "Exiting mtunnel\n";
5810 $state->{conn
} = $conn;
5812 my $reply_err = sub {
5815 my $reply = JSON
::encode_json
({
5816 success
=> JSON
::false
,
5819 $conn->print("$reply\n");
5823 my $reply_ok = sub {
5826 $res->{success
} = JSON
::true
;
5827 my $reply = JSON
::encode_json
($res);
5828 $conn->print("$reply\n");
5832 while (my $line = <$conn>) {
5835 # untaint, we validate below if needed
5836 ($line) = $line =~ /^(.*)$/;
5837 my $parsed = eval { JSON
::decode_json
($line) };
5839 $reply_err->("failed to parse command - $@");
5843 my $cmd = delete $parsed->{cmd
};
5844 if (!defined($cmd)) {
5845 $reply_err->("'cmd' missing");
5846 } elsif ($state->{exit}) {
5847 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
5849 } elsif (my $handler = $cmd_handlers->{$cmd}) {
5850 print "received command '$cmd'\n";
5852 if ($cmd_desc->{$cmd}) {
5853 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
5857 my $res = $run_locked->($handler, $parsed);
5860 $reply_err->("failed to handle '$cmd' command - $@")
5863 $reply_err->("unknown command '$cmd' given");
5867 if ($state->{exit}) {
5868 print "mtunnel exited\n";
5870 die "mtunnel exited unexpectedly\n";
5874 my $socket_addr = "/run/qemu-server/$vmid.mtunnel";
5875 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
5876 my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);
5881 socket => $socket_addr,
5885 __PACKAGE__-
>register_method({
5886 name
=> 'mtunnelwebsocket',
5887 path
=> '{vmid}/mtunnelwebsocket',
5890 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.",
5891 user
=> 'all', # check inside
5893 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
5895 additionalProperties
=> 0,
5897 node
=> get_standard_option
('pve-node'),
5898 vmid
=> get_standard_option
('pve-vmid'),
5901 description
=> "unix socket to forward to",
5905 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
5912 port
=> { type
=> 'string', optional
=> 1 },
5913 socket => { type
=> 'string', optional
=> 1 },
5919 my $rpcenv = PVE
::RPCEnvironment
::get
();
5920 my $authuser = $rpcenv->get_user();
5922 my $nodename = PVE
::INotify
::nodename
();
5923 my $node = extract_param
($param, 'node');
5925 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5926 if $node ne 'localhost' && $node ne $nodename;
5928 my $vmid = $param->{vmid
};
5930 PVE
::QemuConfig-
>load_config($vmid);
5932 my $socket = $param->{socket};
5933 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
5935 return { socket => $socket };