1 package PVE
::API2
::Qemu
;
12 use Crypt
::OpenSSL
::Random
;
13 use Socket
qw(SOCK_STREAM);
15 use PVE
::APIClient
::LWP
;
17 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
20 use PVE
::Tools
qw(extract_param);
21 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
23 use PVE
::JSONSchema
qw(get_standard_option);
25 use PVE
::ReplicationConfig
;
26 use PVE
::GuestHelpers
qw(assert_tag_permissions);
29 use PVE
::QemuServer
::Cloudinit
;
30 use PVE
::QemuServer
::CPUConfig
;
31 use PVE
::QemuServer
::Drive
;
32 use PVE
::QemuServer
::ImportDisk
;
33 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
34 use PVE
::QemuServer
::Machine
;
35 use PVE
::QemuServer
::PCI
;
36 use PVE
::QemuServer
::USB
;
38 use PVE
::RPCEnvironment
;
39 use PVE
::AccessControl
;
43 use PVE
::API2
::Firewall
::VM
;
44 use PVE
::API2
::Qemu
::Agent
;
45 use PVE
::VZDump
::Plugin
;
46 use PVE
::DataCenterConfig
;
49 use PVE
::StorageTunnel
;
52 if (!$ENV{PVE_GENERATING_DOCS
}) {
53 require PVE
::HA
::Env
::PVE2
;
54 import PVE
::HA
::Env
::PVE2
;
55 require PVE
::HA
::Config
;
56 import PVE
::HA
::Config
;
60 use base
qw(PVE::RESTHandler);
62 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
64 my $resolve_cdrom_alias = sub {
67 if (my $value = $param->{cdrom
}) {
68 $value .= ",media=cdrom" if $value !~ m/media=/;
69 $param->{ide2
} = $value;
70 delete $param->{cdrom
};
74 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
75 my $foreach_volume_with_alloc = sub {
76 my ($param, $func) = @_;
78 for my $opt (sort keys $param->%*) {
79 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
81 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
84 $func->($opt, $drive);
88 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
90 my $check_drive_param = sub {
91 my ($param, $storecfg, $extra_checks) = @_;
93 for my $opt (sort keys $param->%*) {
94 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
96 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
97 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
99 if ($drive->{'import-from'}) {
100 if ($drive->{file
} !~ $NEW_DISK_RE || $3 != 0) {
102 $opt => "'import-from' requires special syntax - ".
103 "use <storage ID>:0,import-from=<source>",
107 if ($opt eq 'efidisk0') {
108 for my $required (qw(efitype pre-enrolled-keys)) {
109 if (!defined($drive->{$required})) {
111 $opt => "need to specify '$required' when using 'import-from'",
115 } elsif ($opt eq 'tpmstate0') {
116 raise_param_exc
({ $opt => "need to specify 'version' when using 'import-from'" })
117 if !defined($drive->{version
});
121 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
123 $extra_checks->($drive) if $extra_checks;
125 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive, 1);
129 my $check_storage_access = sub {
130 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
132 $foreach_volume_with_alloc->($settings, sub {
133 my ($ds, $drive) = @_;
135 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
137 my $volid = $drive->{file
};
138 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
140 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
142 } elsif ($isCDROM && ($volid eq 'cdrom')) {
143 $rpcenv->check($authuser, "/", ['Sys.Console']);
144 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
145 my ($storeid, $size) = ($2 || $default_storage, $3);
146 die "no storage ID specified (and no default storage)\n" if !$storeid;
147 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
148 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
149 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
150 if !$scfg->{content
}->{images
};
152 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
154 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
155 raise_param_exc
({ $ds => "content type needs to be 'images' or 'iso'" })
156 if $vtype ne 'images' && $vtype ne 'iso';
160 if (my $src_image = $drive->{'import-from'}) {
162 if (PVE
::Storage
::parse_volume_id
($src_image, 1)) { # PVE-managed volume
163 (my $vtype, undef, $src_vmid) = PVE
::Storage
::parse_volname
($storecfg, $src_image);
164 raise_param_exc
({ $ds => "$src_image has wrong type '$vtype' - not an image" })
165 if $vtype ne 'images';
168 if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()
169 $rpcenv->check($authuser, "/vms/${src_vmid}", ['VM.Clone']);
171 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $src_image);
176 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
177 if defined($settings->{vmstatestorage
});
180 my $check_storage_access_clone = sub {
181 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
185 PVE
::QemuConfig-
>foreach_volume($conf, sub {
186 my ($ds, $drive) = @_;
188 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
190 my $volid = $drive->{file
};
192 return if !$volid || $volid eq 'none';
195 if ($volid eq 'cdrom') {
196 $rpcenv->check($authuser, "/", ['Sys.Console']);
198 # we simply allow access
199 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
200 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
201 $sharedvm = 0 if !$scfg->{shared
};
205 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
206 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
207 $sharedvm = 0 if !$scfg->{shared
};
209 $sid = $storage if $storage;
210 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
214 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
215 if defined($conf->{vmstatestorage
});
220 my $check_storage_access_migrate = sub {
221 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
223 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
225 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
227 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
228 die "storage '$storage' does not support vm images\n"
229 if !$scfg->{content
}->{images
};
232 my $import_from_volid = sub {
233 my ($storecfg, $src_volid, $dest_info, $vollist) = @_;
235 die "could not get size of $src_volid\n"
236 if !PVE
::Storage
::volume_size_info
($storecfg, $src_volid, 10);
238 die "cannot import from cloudinit disk\n"
239 if PVE
::QemuServer
::Drive
::drive_is_cloudinit
({ file
=> $src_volid });
241 my $src_vmid = (PVE
::Storage
::parse_volname
($storecfg, $src_volid))[2];
243 my $src_vm_state = sub {
244 my $exists = $src_vmid && PVE
::Cluster
::get_vmlist
()->{ids
}->{$src_vmid} ?
1 : 0;
248 eval { PVE
::QemuConfig
::assert_config_exists_on_node
($src_vmid); };
249 die "owner VM $src_vmid not on local node\n" if $@;
250 $runs = PVE
::QemuServer
::Helpers
::vm_running_locally
($src_vmid) || 0;
253 return ($exists, $runs);
256 my ($src_vm_exists, $running) = $src_vm_state->();
258 die "cannot import from '$src_volid' - full clone feature is not supported\n"
259 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $src_volid, undef, $running);
262 my ($src_vm_exists_now, $running_now) = $src_vm_state->();
264 die "owner VM $src_vmid changed state unexpectedly\n"
265 if $src_vm_exists_now != $src_vm_exists || $running_now != $running;
267 my $src_conf = $src_vm_exists_now ? PVE
::QemuConfig-
>load_config($src_vmid) : {};
269 my $src_drive = { file
=> $src_volid };
271 PVE
::QemuConfig-
>foreach_volume($src_conf, sub {
272 my ($ds, $drive) = @_;
274 return if $src_drivename;
276 if ($drive->{file
} eq $src_volid) {
278 $src_drivename = $ds;
284 running
=> $running_now,
285 drivename
=> $src_drivename,
290 my ($src_storeid) = PVE
::Storage
::parse_volume_id
($src_volid);
292 return PVE
::QemuServer
::clone_disk
(
301 PVE
::Storage
::get_bandwidth_limit
('clone', [$src_storeid, $dest_info->{storage
}]),
307 $cloned = PVE
::QemuConfig-
>lock_config_full($src_vmid, 30, $clonefn);
308 } elsif ($src_vmid) {
309 $cloned = PVE
::QemuConfig-
>lock_config_shared($src_vmid, 30, $clonefn);
311 $cloned = $clonefn->();
314 return $cloned->@{qw(file size)};
317 # Note: $pool is only needed when creating a VM, because pool permissions
318 # are automatically inherited if VM already exists inside a pool.
319 my $create_disks = sub {
320 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
327 my ($ds, $disk) = @_;
329 my $volid = $disk->{file
};
330 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
332 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
333 delete $disk->{size
};
334 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
335 } elsif (defined($volname) && $volname eq 'cloudinit') {
336 $storeid = $storeid // $default_storage;
337 die "no storage ID specified (and no default storage)\n" if !$storeid;
340 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
341 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
342 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
344 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
347 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
348 my $name = "vm-$vmid-cloudinit";
352 $fmt = $disk->{format
} // "qcow2";
355 $fmt = $disk->{format
} // "raw";
358 # Initial disk created with 4 MB and aligned to 4MB on regeneration
359 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
360 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
361 $disk->{file
} = $volid;
362 $disk->{media
} = 'cdrom';
363 push @$vollist, $volid;
364 delete $disk->{format
}; # no longer needed
365 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
366 print "$ds: successfully created disk '$res->{$ds}'\n";
367 } elsif ($volid =~ $NEW_DISK_RE) {
368 my ($storeid, $size) = ($2 || $default_storage, $3);
369 die "no storage ID specified (and no default storage)\n" if !$storeid;
371 if (my $source = delete $disk->{'import-from'}) {
374 if (PVE
::Storage
::parse_volume_id
($source, 1)) { # PVE-managed volume
379 format
=> $disk->{format
},
382 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf, $disk)
383 if $ds eq 'efidisk0';
385 ($dst_volid, $size) = eval {
386 $import_from_volid->($storecfg, $source, $dest_info, $vollist);
388 die "cannot import from '$source' - $@" if $@;
390 $source = PVE
::Storage
::abs_filesystem_path
($storecfg, $source, 1);
391 $size = PVE
::Storage
::file_size_info
($source);
392 die "could not get file size of $source\n" if !$size;
394 (undef, $dst_volid) = PVE
::QemuServer
::ImportDisk
::do_import
(
400 format
=> $disk->{format
},
401 'skip-config-update' => 1,
404 push @$vollist, $dst_volid;
407 $disk->{file
} = $dst_volid;
408 $disk->{size
} = $size;
409 delete $disk->{format
}; # no longer needed
410 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
412 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
413 my $fmt = $disk->{format
} || $defformat;
415 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
418 if ($ds eq 'efidisk0') {
419 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
420 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
421 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
422 } elsif ($ds eq 'tpmstate0') {
423 # swtpm can only use raw volumes, and uses a fixed size
424 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
425 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
427 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
429 push @$vollist, $volid;
430 $disk->{file
} = $volid;
431 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
432 delete $disk->{format
}; # no longer needed
433 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
436 print "$ds: successfully created disk '$res->{$ds}'\n";
438 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
440 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
441 die "cannot use volume $volid - content type needs to be 'images' or 'iso'"
442 if $vtype ne 'images' && $vtype ne 'iso';
444 if (PVE
::QemuServer
::Drive
::drive_is_cloudinit
($disk)) {
446 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
447 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
448 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
450 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
455 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
457 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
458 die "volume $volid does not exist\n" if !$size;
459 $disk->{size
} = $size;
461 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
465 eval { $foreach_volume_with_alloc->($settings, $code); };
467 # free allocated images on error
469 syslog
('err', "VM $vmid creating disks failed");
470 foreach my $volid (@$vollist) {
471 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
477 return ($vollist, $res);
480 my $check_cpu_model_access = sub {
481 my ($rpcenv, $authuser, $new, $existing) = @_;
483 return if !defined($new->{cpu
});
485 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
486 return if !$cpu || !$cpu->{cputype
}; # always allow default
487 my $cputype = $cpu->{cputype
};
489 if ($existing && $existing->{cpu
}) {
490 # changing only other settings doesn't require permissions for CPU model
491 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
492 return if $existingCpu->{cputype
} eq $cputype;
495 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
496 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
511 my $memoryoptions = {
517 my $hwtypeoptions = {
530 my $generaloptions = {
537 'migrate_downtime' => 1,
538 'migrate_speed' => 1,
550 my $vmpoweroptions = {
557 'vmstatestorage' => 1,
560 my $cloudinitoptions = {
570 my $check_vm_create_serial_perm = sub {
571 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
573 return 1 if $authuser eq 'root@pam';
575 foreach my $opt (keys %{$param}) {
576 next if $opt !~ m/^serial\d+$/;
578 if ($param->{$opt} eq 'socket') {
579 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
581 die "only root can set '$opt' config for real devices\n";
588 my sub check_usb_perm
{
589 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
591 return 1 if $authuser eq 'root@pam';
593 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
595 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-usb', $value);
596 if ($device->{host
} && $device->{host
} !~ m/^spice$/i) {
597 die "only root can set '$opt' config for real devices\n";
598 } elsif ($device->{mapping
}) {
599 $rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
601 die "either 'host' or 'mapping' must be set.\n";
607 my sub check_vm_create_usb_perm
{
608 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
610 return 1 if $authuser eq 'root@pam';
612 foreach my $opt (keys %{$param}) {
613 next if $opt !~ m/^usb\d+$/;
614 check_usb_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
620 my sub check_hostpci_perm
{
621 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
623 return 1 if $authuser eq 'root@pam';
625 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-hostpci', $value);
626 if ($device->{host
}) {
627 die "only root can set '$opt' config for non-mapped devices\n";
628 } elsif ($device->{mapping
}) {
629 $rpcenv->check_full($authuser, "/mapping/pci/$device->{mapping}", ['Mapping.Use']);
630 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
632 die "either 'host' or 'mapping' must be set.\n";
638 my sub check_vm_create_hostpci_perm
{
639 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
641 return 1 if $authuser eq 'root@pam';
643 foreach my $opt (keys %{$param}) {
644 next if $opt !~ m/^hostpci\d+$/;
645 check_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
651 my $check_vm_modify_config_perm = sub {
652 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
654 return 1 if $authuser eq 'root@pam';
656 foreach my $opt (@$key_list) {
657 # some checks (e.g., disk, serial port, usb) need to be done somewhere
658 # else, as there the permission can be value dependend
659 next if PVE
::QemuServer
::is_valid_drivename
($opt);
660 next if $opt eq 'cdrom';
661 next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
662 next if $opt eq 'tags';
665 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
666 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
667 } elsif ($memoryoptions->{$opt}) {
668 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
669 } elsif ($hwtypeoptions->{$opt}) {
670 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
671 } elsif ($generaloptions->{$opt}) {
672 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
673 # special case for startup since it changes host behaviour
674 if ($opt eq 'startup') {
675 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
677 } elsif ($vmpoweroptions->{$opt}) {
678 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
679 } elsif ($diskoptions->{$opt}) {
680 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
681 } elsif ($opt =~ m/^net\d+$/) {
682 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
683 } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\d+$/) {
684 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
685 } elsif ($opt eq 'vmstate') {
686 # the user needs Disk and PowerMgmt privileges to change the vmstate
687 # also needs privileges on the storage, that will be checked later
688 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
690 # catches args, lock, etc.
691 # new options will be checked here
692 die "only root can set '$opt' config\n";
699 __PACKAGE__-
>register_method({
703 description
=> "Virtual machine index (per node).",
705 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
709 protected
=> 1, # qemu pid files are only readable by root
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
717 description
=> "Determine the full status of active VMs.",
725 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
727 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
732 my $rpcenv = PVE
::RPCEnvironment
::get
();
733 my $authuser = $rpcenv->get_user();
735 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
738 foreach my $vmid (keys %$vmstatus) {
739 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
741 my $data = $vmstatus->{$vmid};
748 my $parse_restore_archive = sub {
749 my ($storecfg, $archive) = @_;
751 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
753 if (defined($archive_storeid)) {
754 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
755 if ($scfg->{type
} eq 'pbs') {
762 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
770 __PACKAGE__-
>register_method({
774 description
=> "Create or restore a virtual machine.",
776 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
777 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
778 "If you create disks you need 'Datastore.AllocateSpace' on any used storage." .
779 "If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.",
780 user
=> 'all', # check inside
785 additionalProperties
=> 0,
786 properties
=> PVE
::QemuServer
::json_config_properties
(
788 node
=> get_standard_option
('pve-node'),
789 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
791 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
795 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
797 storage
=> get_standard_option
('pve-storage-id', {
798 description
=> "Default storage.",
800 completion
=> \
&PVE
::QemuServer
::complete_storage
,
805 description
=> "Allow to overwrite existing VM.",
806 requires
=> 'archive',
811 description
=> "Assign a unique random ethernet address.",
812 requires
=> 'archive',
817 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
818 requires
=> 'archive',
822 type
=> 'string', format
=> 'pve-poolid',
823 description
=> "Add the VM to the specified pool.",
826 description
=> "Override I/O bandwidth limit (in KiB/s).",
830 default => 'restore limit from datacenter or storage config',
836 description
=> "Start VM after it was created successfully.",
848 my $rpcenv = PVE
::RPCEnvironment
::get
();
849 my $authuser = $rpcenv->get_user();
851 my $node = extract_param
($param, 'node');
852 my $vmid = extract_param
($param, 'vmid');
854 my $archive = extract_param
($param, 'archive');
855 my $is_restore = !!$archive;
857 my $bwlimit = extract_param
($param, 'bwlimit');
858 my $force = extract_param
($param, 'force');
859 my $pool = extract_param
($param, 'pool');
860 my $start_after_create = extract_param
($param, 'start');
861 my $storage = extract_param
($param, 'storage');
862 my $unique = extract_param
($param, 'unique');
863 my $live_restore = extract_param
($param, 'live-restore');
865 if (defined(my $ssh_keys = $param->{sshkeys
})) {
866 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
867 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
870 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
871 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
873 PVE
::Cluster
::check_cfs_quorum
();
875 my $filename = PVE
::QemuConfig-
>config_file($vmid);
876 my $storecfg = PVE
::Storage
::config
();
878 if (defined($pool)) {
879 $rpcenv->check_pool_exist($pool);
882 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
883 if defined($storage);
885 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
887 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
889 } elsif ($archive && $force && (-f
$filename) &&
890 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
891 # OK: user has VM.Backup permissions and wants to restore an existing VM
897 for my $opt (sort keys $param->%*) {
898 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
899 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
903 if ($archive eq '-') {
904 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
905 $archive = { type
=> 'pipe' };
907 PVE
::Storage
::check_volume_access
(
916 $archive = $parse_restore_archive->($storecfg, $archive);
920 if (scalar(keys $param->%*) > 0) {
921 &$resolve_cdrom_alias($param);
923 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
925 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
927 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
928 check_vm_create_usb_perm
($rpcenv, $authuser, $vmid, $pool, $param);
929 check_vm_create_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $param);
931 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
932 &$check_cpu_model_access($rpcenv, $authuser, $param);
934 $check_drive_param->($param, $storecfg);
936 PVE
::QemuServer
::add_random_macs
($param);
939 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
941 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
942 die "$emsg $@" if $@;
944 my $restored_data = 0;
945 my $restorefn = sub {
946 my $conf = PVE
::QemuConfig-
>load_config($vmid);
948 PVE
::QemuConfig-
>check_protection($conf, $emsg);
950 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
953 my $restore_options = {
958 live
=> $live_restore,
959 override_conf
=> $param,
961 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
962 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
964 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
965 } elsif ($archive->{type
} eq 'pbs') {
966 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
968 die "unknown backup archive type\n";
972 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
973 # Convert restored VM to template if backup was VM template
974 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
975 warn "Convert to template.\n";
976 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
981 # ensure no old replication state are exists
982 PVE
::ReplicationState
::delete_guest_states
($vmid);
984 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
986 if ($start_after_create && !$live_restore) {
987 print "Execute autostart\n";
988 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
994 # ensure no old replication state are exists
995 PVE
::ReplicationState
::delete_guest_states
($vmid);
999 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1001 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
1005 ($vollist, my $created_opts) = $create_disks->(
1016 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1018 if (!$conf->{boot
}) {
1019 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
1020 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
1023 # auto generate uuid if user did not specify smbios1 option
1024 if (!$conf->{smbios1
}) {
1025 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
1028 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
1029 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
1032 my $machine = $conf->{machine
};
1033 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
1034 # always pin Windows' machine version on create, they get to easily confused
1035 if (PVE
::QemuServer
::Helpers
::windows_version
($conf->{ostype
})) {
1036 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
1040 PVE
::QemuConfig-
>write_config($vmid, $conf);
1046 foreach my $volid (@$vollist) {
1047 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1053 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
1056 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1058 if ($start_after_create) {
1059 print "Execute autostart\n";
1060 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
1065 my ($code, $worker_name);
1067 $worker_name = 'qmrestore';
1069 eval { $restorefn->() };
1071 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
1073 if ($restored_data) {
1074 warn "error after data was restored, VM disks should be OK but config may "
1075 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1077 warn "error before or during data restore, some or all disks were not "
1078 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1084 $worker_name = 'qmcreate';
1086 eval { $createfn->() };
1089 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1090 unlink($conffile) or die "failed to remove config file: $!\n";
1098 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1101 __PACKAGE__-
>register_method({
1106 description
=> "Directory index",
1111 additionalProperties
=> 0,
1113 node
=> get_standard_option
('pve-node'),
1114 vmid
=> get_standard_option
('pve-vmid'),
1122 subdir
=> { type
=> 'string' },
1125 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1131 { subdir
=> 'config' },
1132 { subdir
=> 'cloudinit' },
1133 { subdir
=> 'pending' },
1134 { subdir
=> 'status' },
1135 { subdir
=> 'unlink' },
1136 { subdir
=> 'vncproxy' },
1137 { subdir
=> 'termproxy' },
1138 { subdir
=> 'migrate' },
1139 { subdir
=> 'resize' },
1140 { subdir
=> 'move' },
1141 { subdir
=> 'rrd' },
1142 { subdir
=> 'rrddata' },
1143 { subdir
=> 'monitor' },
1144 { subdir
=> 'agent' },
1145 { subdir
=> 'snapshot' },
1146 { subdir
=> 'spiceproxy' },
1147 { subdir
=> 'sendkey' },
1148 { subdir
=> 'firewall' },
1149 { subdir
=> 'mtunnel' },
1150 { subdir
=> 'remote_migrate' },
1156 __PACKAGE__-
>register_method ({
1157 subclass
=> "PVE::API2::Firewall::VM",
1158 path
=> '{vmid}/firewall',
1161 __PACKAGE__-
>register_method ({
1162 subclass
=> "PVE::API2::Qemu::Agent",
1163 path
=> '{vmid}/agent',
1166 __PACKAGE__-
>register_method({
1168 path
=> '{vmid}/rrd',
1170 protected
=> 1, # fixme: can we avoid that?
1172 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1174 description
=> "Read VM RRD statistics (returns PNG)",
1176 additionalProperties
=> 0,
1178 node
=> get_standard_option
('pve-node'),
1179 vmid
=> get_standard_option
('pve-vmid'),
1181 description
=> "Specify the time frame you are interested in.",
1183 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1186 description
=> "The list of datasources you want to display.",
1187 type
=> 'string', format
=> 'pve-configid-list',
1190 description
=> "The RRD consolidation function",
1192 enum
=> [ 'AVERAGE', 'MAX' ],
1200 filename
=> { type
=> 'string' },
1206 return PVE
::RRD
::create_rrd_graph
(
1207 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1208 $param->{ds
}, $param->{cf
});
1212 __PACKAGE__-
>register_method({
1214 path
=> '{vmid}/rrddata',
1216 protected
=> 1, # fixme: can we avoid that?
1218 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1220 description
=> "Read VM RRD statistics",
1222 additionalProperties
=> 0,
1224 node
=> get_standard_option
('pve-node'),
1225 vmid
=> get_standard_option
('pve-vmid'),
1227 description
=> "Specify the time frame you are interested in.",
1229 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1232 description
=> "The RRD consolidation function",
1234 enum
=> [ 'AVERAGE', 'MAX' ],
1249 return PVE
::RRD
::create_rrd_data
(
1250 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1254 __PACKAGE__-
>register_method({
1255 name
=> 'vm_config',
1256 path
=> '{vmid}/config',
1259 description
=> "Get the virtual machine configuration with pending configuration " .
1260 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1262 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1265 additionalProperties
=> 0,
1267 node
=> get_standard_option
('pve-node'),
1268 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1270 description
=> "Get current values (instead of pending values).",
1275 snapshot
=> get_standard_option
('pve-snapshot-name', {
1276 description
=> "Fetch config values from given snapshot.",
1279 my ($cmd, $pname, $cur, $args) = @_;
1280 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1286 description
=> "The VM configuration.",
1288 properties
=> PVE
::QemuServer
::json_config_properties
({
1291 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1298 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1299 current
=> "cannot use 'snapshot' parameter with 'current'"})
1300 if ($param->{snapshot
} && $param->{current
});
1303 if ($param->{snapshot
}) {
1304 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1306 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1308 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1313 __PACKAGE__-
>register_method({
1314 name
=> 'vm_pending',
1315 path
=> '{vmid}/pending',
1318 description
=> "Get the virtual machine configuration with both current and pending values.",
1320 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1323 additionalProperties
=> 0,
1325 node
=> get_standard_option
('pve-node'),
1326 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1335 description
=> "Configuration option name.",
1339 description
=> "Current value.",
1344 description
=> "Pending value.",
1349 description
=> "Indicates a pending delete request if present and not 0. " .
1350 "The value 2 indicates a force-delete request.",
1362 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1364 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1366 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1367 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1369 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1372 __PACKAGE__-
>register_method({
1373 name
=> 'cloudinit_pending',
1374 path
=> '{vmid}/cloudinit',
1377 description
=> "Get the cloudinit configuration with both current and pending values.",
1379 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1382 additionalProperties
=> 0,
1384 node
=> get_standard_option
('pve-node'),
1385 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1394 description
=> "Configuration option name.",
1398 description
=> "Value as it was used to generate the current cloudinit image.",
1403 description
=> "The new pending value.",
1408 description
=> "Indicates a pending delete request if present and not 0. ",
1420 my $vmid = $param->{vmid
};
1421 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1423 my $ci = $conf->{cloudinit
};
1425 $conf->{cipassword
} = '**********' if exists $conf->{cipassword
};
1426 $ci->{cipassword
} = '**********' if exists $ci->{cipassword
};
1430 # All the values that got added
1431 my $added = delete($ci->{added
}) // '';
1432 for my $key (PVE
::Tools
::split_list
($added)) {
1433 push @$res, { key
=> $key, pending
=> $conf->{$key} };
1436 # All already existing values (+ their new value, if it exists)
1437 for my $opt (keys %$cloudinitoptions) {
1438 next if !$conf->{$opt};
1439 next if $added =~ m/$opt/;
1444 if (my $pending = $ci->{$opt}) {
1445 $item->{value
} = $pending;
1446 $item->{pending
} = $conf->{$opt};
1448 $item->{value
} = $conf->{$opt},
1454 # Now, we'll find the deleted ones
1455 for my $opt (keys %$ci) {
1456 next if $conf->{$opt};
1457 push @$res, { key
=> $opt, delete => 1 };
1463 __PACKAGE__-
>register_method({
1464 name
=> 'cloudinit_update',
1465 path
=> '{vmid}/cloudinit',
1469 description
=> "Regenerate and change cloudinit config drive.",
1471 check
=> ['perm', '/vms/{vmid}', 'VM.Config.Cloudinit'],
1474 additionalProperties
=> 0,
1476 node
=> get_standard_option
('pve-node'),
1477 vmid
=> get_standard_option
('pve-vmid'),
1480 returns
=> { type
=> 'null' },
1484 my $rpcenv = PVE
::RPCEnvironment
::get
();
1485 my $authuser = $rpcenv->get_user();
1487 my $vmid = extract_param
($param, 'vmid');
1489 PVE
::QemuConfig-
>lock_config($vmid, sub {
1490 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1491 PVE
::QemuConfig-
>check_lock($conf);
1493 my $storecfg = PVE
::Storage
::config
();
1494 PVE
::QemuServer
::vmconfig_update_cloudinit_drive
($storecfg, $conf, $vmid);
1499 # POST/PUT {vmid}/config implementation
1501 # The original API used PUT (idempotent) an we assumed that all operations
1502 # are fast. But it turned out that almost any configuration change can
1503 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1504 # time to complete and have side effects (not idempotent).
1506 # The new implementation uses POST and forks a worker process. We added
1507 # a new option 'background_delay'. If specified we wait up to
1508 # 'background_delay' second for the worker task to complete. It returns null
1509 # if the task is finished within that time, else we return the UPID.
1511 my $update_vm_api = sub {
1512 my ($param, $sync) = @_;
1514 my $rpcenv = PVE
::RPCEnvironment
::get
();
1516 my $authuser = $rpcenv->get_user();
1518 my $node = extract_param
($param, 'node');
1520 my $vmid = extract_param
($param, 'vmid');
1522 my $digest = extract_param
($param, 'digest');
1524 my $background_delay = extract_param
($param, 'background_delay');
1526 my $skip_cloud_init = extract_param
($param, 'skip_cloud_init');
1528 if (defined(my $cipassword = $param->{cipassword
})) {
1529 # Same logic as in cloud-init (but with the regex fixed...)
1530 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1531 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1534 my @paramarr = (); # used for log message
1535 foreach my $key (sort keys %$param) {
1536 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1537 push @paramarr, "-$key", $value;
1540 my $skiplock = extract_param
($param, 'skiplock');
1541 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1542 if $skiplock && $authuser ne 'root@pam';
1544 my $delete_str = extract_param
($param, 'delete');
1546 my $revert_str = extract_param
($param, 'revert');
1548 my $force = extract_param
($param, 'force');
1550 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1551 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1552 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1555 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
1556 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1558 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1560 my $storecfg = PVE
::Storage
::config
();
1562 my $defaults = PVE
::QemuServer
::load_defaults
();
1564 &$resolve_cdrom_alias($param);
1566 # now try to verify all parameters
1569 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1570 if (!PVE
::QemuServer
::option_exists
($opt)) {
1571 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1574 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1575 "-revert $opt' at the same time" })
1576 if defined($param->{$opt});
1578 $revert->{$opt} = 1;
1582 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1583 $opt = 'ide2' if $opt eq 'cdrom';
1585 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1586 "-delete $opt' at the same time" })
1587 if defined($param->{$opt});
1589 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1590 "-revert $opt' at the same time" })
1593 if (!PVE
::QemuServer
::option_exists
($opt)) {
1594 raise_param_exc
({ delete => "unknown option '$opt'" });
1600 my $repl_conf = PVE
::ReplicationConfig-
>new();
1601 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1602 my $check_replication = sub {
1604 return if !$is_replicated;
1605 my $volid = $drive->{file
};
1606 return if !$volid || !($drive->{replicate
}//1);
1607 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1609 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1610 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1611 if !defined($storeid);
1613 return if defined($volname) && $volname eq 'cloudinit';
1616 if ($volid =~ $NEW_DISK_RE) {
1618 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1620 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1622 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1623 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1624 return if $scfg->{shared
};
1625 die "cannot add non-replicatable volume to a replicated VM\n";
1628 $check_drive_param->($param, $storecfg, $check_replication);
1630 foreach my $opt (keys %$param) {
1631 if ($opt =~ m/^net(\d+)$/) {
1633 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1634 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1635 } elsif ($opt eq 'vmgenid') {
1636 if ($param->{$opt} eq '1') {
1637 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1639 } elsif ($opt eq 'hookscript') {
1640 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1641 raise_param_exc
({ $opt => $@ }) if $@;
1645 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1647 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1649 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1651 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
1653 my $updatefn = sub {
1655 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1657 die "checksum missmatch (file change by other user?)\n"
1658 if $digest && $digest ne $conf->{digest
};
1660 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1662 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1663 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1664 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1665 delete $conf->{lock}; # for check lock check, not written out
1666 push @delete, 'lock'; # this is the real deal to write it out
1668 push @delete, 'runningmachine' if $conf->{runningmachine
};
1669 push @delete, 'runningcpu' if $conf->{runningcpu
};
1672 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1674 foreach my $opt (keys %$revert) {
1675 if (defined($conf->{$opt})) {
1676 $param->{$opt} = $conf->{$opt};
1677 } elsif (defined($conf->{pending
}->{$opt})) {
1682 if ($param->{memory
} || defined($param->{balloon
})) {
1683 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1684 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1686 die "balloon value too large (must be smaller than assigned memory)\n"
1687 if $balloon && $balloon > $maxmem;
1690 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1694 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1696 # write updates to pending section
1698 my $modified = {}; # record what $option we modify
1701 if (my $boot = $conf->{boot
}) {
1702 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1703 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1705 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1707 my $check_drive_perms = sub {
1708 my ($opt, $val) = @_;
1709 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1710 if (PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
1711 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Cloudinit', 'VM.Config.CDROM']);
1712 } elsif (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) { # CDROM
1713 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1715 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1720 foreach my $opt (@delete) {
1721 $modified->{$opt} = 1;
1722 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1724 # value of what we want to delete, independent if pending or not
1725 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1726 if (!defined($val)) {
1727 warn "cannot delete '$opt' - not set in current configuration!\n";
1728 $modified->{$opt} = 0;
1731 my $is_pending_val = defined($conf->{pending
}->{$opt});
1732 delete $conf->{pending
}->{$opt};
1734 # remove from bootorder if necessary
1735 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1736 @bootorder = grep {$_ ne $opt} @bootorder;
1737 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1738 $modified->{boot
} = 1;
1741 if ($opt =~ m/^unused/) {
1742 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1743 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1744 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1745 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1746 delete $conf->{$opt};
1747 PVE
::QemuConfig-
>write_config($vmid, $conf);
1749 } elsif ($opt eq 'vmstate') {
1750 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1751 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1752 delete $conf->{$opt};
1753 PVE
::QemuConfig-
>write_config($vmid, $conf);
1755 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1756 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1757 $check_drive_perms->($opt, $val);
1758 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1760 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1761 PVE
::QemuConfig-
>write_config($vmid, $conf);
1762 } elsif ($opt =~ m/^serial\d+$/) {
1763 if ($val eq 'socket') {
1764 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1765 } elsif ($authuser ne 'root@pam') {
1766 die "only root can delete '$opt' config for real devices\n";
1768 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1769 PVE
::QemuConfig-
>write_config($vmid, $conf);
1770 } elsif ($opt =~ m/^usb\d+$/) {
1771 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1772 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1773 PVE
::QemuConfig-
>write_config($vmid, $conf);
1774 } elsif ($opt =~ m/^hostpci\d+$/) {
1775 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1776 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1777 PVE
::QemuConfig-
>write_config($vmid, $conf);
1778 } elsif ($opt eq 'tags') {
1779 assert_tag_permissions
($vmid, $val, '', $rpcenv, $authuser);
1780 delete $conf->{$opt};
1781 PVE
::QemuConfig-
>write_config($vmid, $conf);
1783 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1784 PVE
::QemuConfig-
>write_config($vmid, $conf);
1788 foreach my $opt (keys %$param) { # add/change
1789 $modified->{$opt} = 1;
1790 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1791 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1793 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1795 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1797 if ($conf->{$opt}) {
1798 $check_drive_perms->($opt, $conf->{$opt});
1802 $check_drive_perms->($opt, $param->{$opt});
1803 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1804 if defined($conf->{pending
}->{$opt});
1806 my (undef, $created_opts) = $create_disks->(
1814 {$opt => $param->{$opt}},
1816 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1818 # default legacy boot order implies all cdroms anyway
1820 # append new CD drives to bootorder to mark them bootable
1821 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1822 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1823 push @bootorder, $opt;
1824 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1825 $modified->{boot
} = 1;
1828 } elsif ($opt =~ m/^serial\d+/) {
1829 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1830 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1831 } elsif ($authuser ne 'root@pam') {
1832 die "only root can modify '$opt' config for real devices\n";
1834 $conf->{pending
}->{$opt} = $param->{$opt};
1835 } elsif ($opt =~ m/^usb\d+/) {
1836 if (my $olddevice = $conf->{$opt}) {
1837 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});
1839 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1840 $conf->{pending
}->{$opt} = $param->{$opt};
1841 } elsif ($opt =~ m/^hostpci\d+$/) {
1842 if (my $oldvalue = $conf->{$opt}) {
1843 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);
1845 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1846 $conf->{pending
}->{$opt} = $param->{$opt};
1847 } elsif ($opt eq 'tags') {
1848 assert_tag_permissions
($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
1849 $conf->{pending
}->{$opt} = PVE
::GuestHelpers
::get_unique_tags
($param->{$opt});
1851 $conf->{pending
}->{$opt} = $param->{$opt};
1853 if ($opt eq 'boot') {
1854 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1855 if ($new_bootcfg->{order
}) {
1856 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1857 for my $dev (@devs) {
1858 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1859 my $deleted = grep {$_ eq $dev} @delete;
1860 die "invalid bootorder: device '$dev' does not exist'\n"
1861 if !$exists || $deleted;
1864 # remove legacy boot order settings if new one set
1865 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1866 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1867 if $conf->{bootdisk
};
1871 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1872 PVE
::QemuConfig-
>write_config($vmid, $conf);
1875 # remove pending changes when nothing changed
1876 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1877 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1878 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1880 return if !scalar(keys %{$conf->{pending
}});
1882 my $running = PVE
::QemuServer
::check_running
($vmid);
1884 # apply pending changes
1886 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1890 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1892 # cloud_init must be skipped if we are in an incoming, remote live migration
1893 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors, $skip_cloud_init);
1895 raise_param_exc
($errors) if scalar(keys %$errors);
1904 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1906 if ($background_delay) {
1908 # Note: It would be better to do that in the Event based HTTPServer
1909 # to avoid blocking call to sleep.
1911 my $end_time = time() + $background_delay;
1913 my $task = PVE
::Tools
::upid_decode
($upid);
1916 while (time() < $end_time) {
1917 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1919 sleep(1); # this gets interrupted when child process ends
1923 my $status = PVE
::Tools
::upid_read_status
($upid);
1924 return if !PVE
::Tools
::upid_status_is_error
($status);
1925 die "failed to update VM $vmid: $status\n";
1933 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1936 my $vm_config_perm_list = [
1941 'VM.Config.Network',
1943 'VM.Config.Options',
1944 'VM.Config.Cloudinit',
1947 __PACKAGE__-
>register_method({
1948 name
=> 'update_vm_async',
1949 path
=> '{vmid}/config',
1953 description
=> "Set virtual machine options (asynchrounous API).",
1955 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1958 additionalProperties
=> 0,
1959 properties
=> PVE
::QemuServer
::json_config_properties
(
1961 node
=> get_standard_option
('pve-node'),
1962 vmid
=> get_standard_option
('pve-vmid'),
1963 skiplock
=> get_standard_option
('skiplock'),
1965 type
=> 'string', format
=> 'pve-configid-list',
1966 description
=> "A list of settings you want to delete.",
1970 type
=> 'string', format
=> 'pve-configid-list',
1971 description
=> "Revert a pending change.",
1976 description
=> $opt_force_description,
1978 requires
=> 'delete',
1982 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1986 background_delay
=> {
1988 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1994 1, # with_disk_alloc
2001 code
=> $update_vm_api,
2004 __PACKAGE__-
>register_method({
2005 name
=> 'update_vm',
2006 path
=> '{vmid}/config',
2010 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
2012 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
2015 additionalProperties
=> 0,
2016 properties
=> PVE
::QemuServer
::json_config_properties
(
2018 node
=> get_standard_option
('pve-node'),
2019 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2020 skiplock
=> get_standard_option
('skiplock'),
2022 type
=> 'string', format
=> 'pve-configid-list',
2023 description
=> "A list of settings you want to delete.",
2027 type
=> 'string', format
=> 'pve-configid-list',
2028 description
=> "Revert a pending change.",
2033 description
=> $opt_force_description,
2035 requires
=> 'delete',
2039 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2044 1, # with_disk_alloc
2047 returns
=> { type
=> 'null' },
2050 &$update_vm_api($param, 1);
2055 __PACKAGE__-
>register_method({
2056 name
=> 'destroy_vm',
2061 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
2062 ." and firewall rules",
2064 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2067 additionalProperties
=> 0,
2069 node
=> get_standard_option
('pve-node'),
2070 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2071 skiplock
=> get_standard_option
('skiplock'),
2074 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
2077 'destroy-unreferenced-disks' => {
2079 description
=> "If set, destroy additionally all disks not referenced in the config"
2080 ." but with a matching VMID from all enabled storages.",
2092 my $rpcenv = PVE
::RPCEnvironment
::get
();
2093 my $authuser = $rpcenv->get_user();
2094 my $vmid = $param->{vmid
};
2096 my $skiplock = $param->{skiplock
};
2097 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2098 if $skiplock && $authuser ne 'root@pam';
2100 my $early_checks = sub {
2102 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2103 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
2105 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
2107 if (!$param->{purge
}) {
2108 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
2110 # don't allow destroy if with replication jobs but no purge param
2111 my $repl_conf = PVE
::ReplicationConfig-
>new();
2112 $repl_conf->check_for_existing_jobs($vmid);
2115 die "VM $vmid is running - destroy failed\n"
2116 if PVE
::QemuServer
::check_running
($vmid);
2126 my $storecfg = PVE
::Storage
::config
();
2128 syslog
('info', "destroy VM $vmid: $upid\n");
2129 PVE
::QemuConfig-
>lock_config($vmid, sub {
2130 # repeat, config might have changed
2131 my $ha_managed = $early_checks->();
2133 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
2135 PVE
::QemuServer
::destroy_vm
(
2138 $skiplock, { lock => 'destroyed' },
2139 $purge_unreferenced,
2142 PVE
::AccessControl
::remove_vm_access
($vmid);
2143 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2144 if ($param->{purge
}) {
2145 print "purging VM $vmid from related configurations..\n";
2146 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2147 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2150 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2151 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2155 # only now remove the zombie config, else we can have reuse race
2156 PVE
::QemuConfig-
>destroy_config($vmid);
2160 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2163 __PACKAGE__-
>register_method({
2165 path
=> '{vmid}/unlink',
2169 description
=> "Unlink/delete disk images.",
2171 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2174 additionalProperties
=> 0,
2176 node
=> get_standard_option
('pve-node'),
2177 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2179 type
=> 'string', format
=> 'pve-configid-list',
2180 description
=> "A list of disk IDs you want to delete.",
2184 description
=> $opt_force_description,
2189 returns
=> { type
=> 'null'},
2193 $param->{delete} = extract_param
($param, 'idlist');
2195 __PACKAGE__-
>update_vm($param);
2200 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2201 my $gen_rand_chars = sub {
2204 die "invalid length $length" if $length < 1;
2206 my $min = ord('!'); # first printable ascii
2208 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2209 die "failed to generate random bytes!\n"
2212 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2219 __PACKAGE__-
>register_method({
2221 path
=> '{vmid}/vncproxy',
2225 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2227 description
=> "Creates a TCP VNC proxy connections.",
2229 additionalProperties
=> 0,
2231 node
=> get_standard_option
('pve-node'),
2232 vmid
=> get_standard_option
('pve-vmid'),
2236 description
=> "starts websockify instead of vncproxy",
2238 'generate-password' => {
2242 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2247 additionalProperties
=> 0,
2249 user
=> { type
=> 'string' },
2250 ticket
=> { type
=> 'string' },
2253 description
=> "Returned if requested with 'generate-password' param."
2254 ." Consists of printable ASCII characters ('!' .. '~').",
2257 cert
=> { type
=> 'string' },
2258 port
=> { type
=> 'integer' },
2259 upid
=> { type
=> 'string' },
2265 my $rpcenv = PVE
::RPCEnvironment
::get
();
2267 my $authuser = $rpcenv->get_user();
2269 my $vmid = $param->{vmid
};
2270 my $node = $param->{node
};
2271 my $websocket = $param->{websocket
};
2273 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2277 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2278 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2281 my $authpath = "/vms/$vmid";
2283 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2284 my $password = $ticket;
2285 if ($param->{'generate-password'}) {
2286 $password = $gen_rand_chars->(8);
2289 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2295 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2296 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2297 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2298 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2299 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2301 $family = PVE
::Tools
::get_host_address_family
($node);
2304 my $port = PVE
::Tools
::next_vnc_port
($family);
2311 syslog
('info', "starting vnc proxy $upid\n");
2315 if (defined($serial)) {
2317 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2319 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2320 '-timeout', $timeout, '-authpath', $authpath,
2321 '-perm', 'Sys.Console'];
2323 if ($param->{websocket
}) {
2324 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2325 push @$cmd, '-notls', '-listen', 'localhost';
2328 push @$cmd, '-c', @$remcmd, @$termcmd;
2330 PVE
::Tools
::run_command
($cmd);
2334 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
2336 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2338 my $sock = IO
::Socket
::IP-
>new(
2343 GetAddrInfoFlags
=> 0,
2344 ) or die "failed to create socket: $!\n";
2345 # Inside the worker we shouldn't have any previous alarms
2346 # running anyway...:
2348 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2350 accept(my $cli, $sock) or die "connection failed: $!\n";
2353 if (PVE
::Tools
::run_command
($cmd,
2354 output
=> '>&'.fileno($cli),
2355 input
=> '<&'.fileno($cli),
2358 die "Failed to run vncproxy.\n";
2365 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2367 PVE
::Tools
::wait_for_vnc_port
($port);
2376 $res->{password
} = $password if $param->{'generate-password'};
2381 __PACKAGE__-
>register_method({
2382 name
=> 'termproxy',
2383 path
=> '{vmid}/termproxy',
2387 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2389 description
=> "Creates a TCP proxy connections.",
2391 additionalProperties
=> 0,
2393 node
=> get_standard_option
('pve-node'),
2394 vmid
=> get_standard_option
('pve-vmid'),
2398 enum
=> [qw(serial0 serial1 serial2 serial3)],
2399 description
=> "opens a serial terminal (defaults to display)",
2404 additionalProperties
=> 0,
2406 user
=> { type
=> 'string' },
2407 ticket
=> { type
=> 'string' },
2408 port
=> { type
=> 'integer' },
2409 upid
=> { type
=> 'string' },
2415 my $rpcenv = PVE
::RPCEnvironment
::get
();
2417 my $authuser = $rpcenv->get_user();
2419 my $vmid = $param->{vmid
};
2420 my $node = $param->{node
};
2421 my $serial = $param->{serial
};
2423 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2425 if (!defined($serial)) {
2427 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2428 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2432 my $authpath = "/vms/$vmid";
2434 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2439 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2440 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2441 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2442 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2443 push @$remcmd, '--';
2445 $family = PVE
::Tools
::get_host_address_family
($node);
2448 my $port = PVE
::Tools
::next_vnc_port
($family);
2450 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2451 push @$termcmd, '-iface', $serial if $serial;
2456 syslog
('info', "starting qemu termproxy $upid\n");
2458 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2459 '--perm', 'VM.Console', '--'];
2460 push @$cmd, @$remcmd, @$termcmd;
2462 PVE
::Tools
::run_command
($cmd);
2465 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2467 PVE
::Tools
::wait_for_vnc_port
($port);
2477 __PACKAGE__-
>register_method({
2478 name
=> 'vncwebsocket',
2479 path
=> '{vmid}/vncwebsocket',
2482 description
=> "You also need to pass a valid ticket (vncticket).",
2483 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2485 description
=> "Opens a weksocket for VNC traffic.",
2487 additionalProperties
=> 0,
2489 node
=> get_standard_option
('pve-node'),
2490 vmid
=> get_standard_option
('pve-vmid'),
2492 description
=> "Ticket from previous call to vncproxy.",
2497 description
=> "Port number returned by previous vncproxy call.",
2507 port
=> { type
=> 'string' },
2513 my $rpcenv = PVE
::RPCEnvironment
::get
();
2515 my $authuser = $rpcenv->get_user();
2517 my $vmid = $param->{vmid
};
2518 my $node = $param->{node
};
2520 my $authpath = "/vms/$vmid";
2522 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2524 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2526 # Note: VNC ports are acessible from outside, so we do not gain any
2527 # security if we verify that $param->{port} belongs to VM $vmid. This
2528 # check is done by verifying the VNC ticket (inside VNC protocol).
2530 my $port = $param->{port
};
2532 return { port
=> $port };
2535 __PACKAGE__-
>register_method({
2536 name
=> 'spiceproxy',
2537 path
=> '{vmid}/spiceproxy',
2542 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2544 description
=> "Returns a SPICE configuration to connect to the VM.",
2546 additionalProperties
=> 0,
2548 node
=> get_standard_option
('pve-node'),
2549 vmid
=> get_standard_option
('pve-vmid'),
2550 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2553 returns
=> get_standard_option
('remote-viewer-config'),
2557 my $rpcenv = PVE
::RPCEnvironment
::get
();
2559 my $authuser = $rpcenv->get_user();
2561 my $vmid = $param->{vmid
};
2562 my $node = $param->{node
};
2563 my $proxy = $param->{proxy
};
2565 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2566 my $title = "VM $vmid";
2567 $title .= " - ". $conf->{name
} if $conf->{name
};
2569 my $port = PVE
::QemuServer
::spice_port
($vmid);
2571 my ($ticket, undef, $remote_viewer_config) =
2572 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2574 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2575 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2577 return $remote_viewer_config;
2580 __PACKAGE__-
>register_method({
2582 path
=> '{vmid}/status',
2585 description
=> "Directory index",
2590 additionalProperties
=> 0,
2592 node
=> get_standard_option
('pve-node'),
2593 vmid
=> get_standard_option
('pve-vmid'),
2601 subdir
=> { type
=> 'string' },
2604 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2610 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2613 { subdir
=> 'current' },
2614 { subdir
=> 'start' },
2615 { subdir
=> 'stop' },
2616 { subdir
=> 'reset' },
2617 { subdir
=> 'shutdown' },
2618 { subdir
=> 'suspend' },
2619 { subdir
=> 'reboot' },
2625 __PACKAGE__-
>register_method({
2626 name
=> 'vm_status',
2627 path
=> '{vmid}/status/current',
2630 protected
=> 1, # qemu pid files are only readable by root
2631 description
=> "Get virtual machine status.",
2633 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2636 additionalProperties
=> 0,
2638 node
=> get_standard_option
('pve-node'),
2639 vmid
=> get_standard_option
('pve-vmid'),
2645 %$PVE::QemuServer
::vmstatus_return_properties
,
2647 description
=> "HA manager service status.",
2651 description
=> "QEMU VGA configuration supports spice.",
2656 description
=> "QEMU Guest Agent is enabled in config.",
2666 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2668 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2669 my $status = $vmstatus->{$param->{vmid
}};
2671 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2674 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2675 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2676 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2677 $status->{spice
} = 1 if $spice;
2679 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2684 __PACKAGE__-
>register_method({
2686 path
=> '{vmid}/status/start',
2690 description
=> "Start virtual machine.",
2692 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2695 additionalProperties
=> 0,
2697 node
=> get_standard_option
('pve-node'),
2698 vmid
=> get_standard_option
('pve-vmid',
2699 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2700 skiplock
=> get_standard_option
('skiplock'),
2701 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2702 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2705 enum
=> ['secure', 'insecure'],
2706 description
=> "Migration traffic is encrypted using an SSH " .
2707 "tunnel by default. On secure, completely private networks " .
2708 "this can be disabled to increase performance.",
2711 migration_network
=> {
2712 type
=> 'string', format
=> 'CIDR',
2713 description
=> "CIDR of the (sub) network that is used for migration.",
2716 machine
=> get_standard_option
('pve-qemu-machine'),
2718 description
=> "Override QEMU's -cpu argument with the given string.",
2722 targetstorage
=> get_standard_option
('pve-targetstorage'),
2724 description
=> "Wait maximal timeout seconds.",
2727 default => 'max(30, vm memory in GiB)',
2738 my $rpcenv = PVE
::RPCEnvironment
::get
();
2739 my $authuser = $rpcenv->get_user();
2741 my $node = extract_param
($param, 'node');
2742 my $vmid = extract_param
($param, 'vmid');
2743 my $timeout = extract_param
($param, 'timeout');
2744 my $machine = extract_param
($param, 'machine');
2746 my $get_root_param = sub {
2747 my $value = extract_param
($param, $_[0]);
2748 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2749 if $value && $authuser ne 'root@pam';
2753 my $stateuri = $get_root_param->('stateuri');
2754 my $skiplock = $get_root_param->('skiplock');
2755 my $migratedfrom = $get_root_param->('migratedfrom');
2756 my $migration_type = $get_root_param->('migration_type');
2757 my $migration_network = $get_root_param->('migration_network');
2758 my $targetstorage = $get_root_param->('targetstorage');
2759 my $force_cpu = $get_root_param->('force-cpu');
2763 if ($targetstorage) {
2764 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2766 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2767 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2771 # read spice ticket from STDIN
2773 my $nbd_protocol_version = 0;
2774 my $replicated_volumes = {};
2775 my $offline_volumes = {};
2776 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2777 while (defined(my $line = <STDIN
>)) {
2779 if ($line =~ m/^spice_ticket: (.+)$/) {
2781 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2782 $nbd_protocol_version = $1;
2783 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2784 $replicated_volumes->{$1} = 1;
2785 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2786 $offline_volumes->{tpmstate0
} = $1;
2787 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2788 $offline_volumes->{$1} = $2;
2789 } elsif (!$spice_ticket) {
2790 # fallback for old source node
2791 $spice_ticket = $line;
2793 warn "unknown 'start' parameter on STDIN: '$line'\n";
2798 PVE
::Cluster
::check_cfs_quorum
();
2800 my $storecfg = PVE
::Storage
::config
();
2802 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2806 print "Requesting HA start for VM $vmid\n";
2808 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2809 PVE
::Tools
::run_command
($cmd);
2813 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2820 syslog
('info', "start VM $vmid: $upid\n");
2822 my $migrate_opts = {
2823 migratedfrom
=> $migratedfrom,
2824 spice_ticket
=> $spice_ticket,
2825 network
=> $migration_network,
2826 type
=> $migration_type,
2827 storagemap
=> $storagemap,
2828 nbd_proto_version
=> $nbd_protocol_version,
2829 replicated_volumes
=> $replicated_volumes,
2830 offline_volumes
=> $offline_volumes,
2834 statefile
=> $stateuri,
2835 skiplock
=> $skiplock,
2836 forcemachine
=> $machine,
2837 timeout
=> $timeout,
2838 forcecpu
=> $force_cpu,
2841 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2845 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2849 __PACKAGE__-
>register_method({
2851 path
=> '{vmid}/status/stop',
2855 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2856 "is akin to pulling the power plug of a running computer and may damage the VM data",
2858 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2861 additionalProperties
=> 0,
2863 node
=> get_standard_option
('pve-node'),
2864 vmid
=> get_standard_option
('pve-vmid',
2865 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2866 skiplock
=> get_standard_option
('skiplock'),
2867 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2869 description
=> "Wait maximal timeout seconds.",
2875 description
=> "Do not deactivate storage volumes.",
2888 my $rpcenv = PVE
::RPCEnvironment
::get
();
2889 my $authuser = $rpcenv->get_user();
2891 my $node = extract_param
($param, 'node');
2892 my $vmid = extract_param
($param, 'vmid');
2894 my $skiplock = extract_param
($param, 'skiplock');
2895 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2896 if $skiplock && $authuser ne 'root@pam';
2898 my $keepActive = extract_param
($param, 'keepActive');
2899 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2900 if $keepActive && $authuser ne 'root@pam';
2902 my $migratedfrom = extract_param
($param, 'migratedfrom');
2903 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2904 if $migratedfrom && $authuser ne 'root@pam';
2907 my $storecfg = PVE
::Storage
::config
();
2909 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2914 print "Requesting HA stop for VM $vmid\n";
2916 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2917 PVE
::Tools
::run_command
($cmd);
2921 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2927 syslog
('info', "stop VM $vmid: $upid\n");
2929 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2930 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2934 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2938 __PACKAGE__-
>register_method({
2940 path
=> '{vmid}/status/reset',
2944 description
=> "Reset virtual machine.",
2946 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2949 additionalProperties
=> 0,
2951 node
=> get_standard_option
('pve-node'),
2952 vmid
=> get_standard_option
('pve-vmid',
2953 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2954 skiplock
=> get_standard_option
('skiplock'),
2963 my $rpcenv = PVE
::RPCEnvironment
::get
();
2965 my $authuser = $rpcenv->get_user();
2967 my $node = extract_param
($param, 'node');
2969 my $vmid = extract_param
($param, 'vmid');
2971 my $skiplock = extract_param
($param, 'skiplock');
2972 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2973 if $skiplock && $authuser ne 'root@pam';
2975 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2980 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2985 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2988 __PACKAGE__-
>register_method({
2989 name
=> 'vm_shutdown',
2990 path
=> '{vmid}/status/shutdown',
2994 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2995 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2997 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3000 additionalProperties
=> 0,
3002 node
=> get_standard_option
('pve-node'),
3003 vmid
=> get_standard_option
('pve-vmid',
3004 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3005 skiplock
=> get_standard_option
('skiplock'),
3007 description
=> "Wait maximal timeout seconds.",
3013 description
=> "Make sure the VM stops.",
3019 description
=> "Do not deactivate storage volumes.",
3032 my $rpcenv = PVE
::RPCEnvironment
::get
();
3033 my $authuser = $rpcenv->get_user();
3035 my $node = extract_param
($param, 'node');
3036 my $vmid = extract_param
($param, 'vmid');
3038 my $skiplock = extract_param
($param, 'skiplock');
3039 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3040 if $skiplock && $authuser ne 'root@pam';
3042 my $keepActive = extract_param
($param, 'keepActive');
3043 raise_param_exc
({ keepActive
=> "Only root may use this option." })
3044 if $keepActive && $authuser ne 'root@pam';
3046 my $storecfg = PVE
::Storage
::config
();
3050 # if vm is paused, do not shutdown (but stop if forceStop = 1)
3051 # otherwise, we will infer a shutdown command, but run into the timeout,
3052 # then when the vm is resumed, it will instantly shutdown
3054 # checking the qmp status here to get feedback to the gui/cli/api
3055 # and the status query should not take too long
3056 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
3057 if ($param->{forceStop
}) {
3058 warn "VM is paused - stop instead of shutdown\n";
3061 die "VM is paused - cannot shutdown\n";
3065 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3067 my $timeout = $param->{timeout
} // 60;
3071 print "Requesting HA stop for VM $vmid\n";
3073 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
3074 PVE
::Tools
::run_command
($cmd);
3078 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3085 syslog
('info', "shutdown VM $vmid: $upid\n");
3087 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
3088 $shutdown, $param->{forceStop
}, $keepActive);
3092 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
3096 __PACKAGE__-
>register_method({
3097 name
=> 'vm_reboot',
3098 path
=> '{vmid}/status/reboot',
3102 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
3104 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3107 additionalProperties
=> 0,
3109 node
=> get_standard_option
('pve-node'),
3110 vmid
=> get_standard_option
('pve-vmid',
3111 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3113 description
=> "Wait maximal timeout seconds for the shutdown.",
3126 my $rpcenv = PVE
::RPCEnvironment
::get
();
3127 my $authuser = $rpcenv->get_user();
3129 my $node = extract_param
($param, 'node');
3130 my $vmid = extract_param
($param, 'vmid');
3132 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
3134 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3139 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3140 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3144 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3147 __PACKAGE__-
>register_method({
3148 name
=> 'vm_suspend',
3149 path
=> '{vmid}/status/suspend',
3153 description
=> "Suspend virtual machine.",
3155 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3156 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3157 " on the storage for the vmstate.",
3158 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3161 additionalProperties
=> 0,
3163 node
=> get_standard_option
('pve-node'),
3164 vmid
=> get_standard_option
('pve-vmid',
3165 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3166 skiplock
=> get_standard_option
('skiplock'),
3171 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3173 statestorage
=> get_standard_option
('pve-storage-id', {
3174 description
=> "The storage for the VM state",
3175 requires
=> 'todisk',
3177 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3187 my $rpcenv = PVE
::RPCEnvironment
::get
();
3188 my $authuser = $rpcenv->get_user();
3190 my $node = extract_param
($param, 'node');
3191 my $vmid = extract_param
($param, 'vmid');
3193 my $todisk = extract_param
($param, 'todisk') // 0;
3195 my $statestorage = extract_param
($param, 'statestorage');
3197 my $skiplock = extract_param
($param, 'skiplock');
3198 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3199 if $skiplock && $authuser ne 'root@pam';
3201 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3203 die "Cannot suspend HA managed VM to disk\n"
3204 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3206 # early check for storage permission, for better user feedback
3208 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3209 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3211 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3212 for my $key (keys %$conf) {
3213 next if $key !~ /^hostpci\d+/;
3214 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3215 ." possibility to save/restore their internal state\n";
3218 if (!$statestorage) {
3219 # get statestorage from config if none is given
3220 my $storecfg = PVE
::Storage
::config
();
3221 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3224 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3230 syslog
('info', "suspend VM $vmid: $upid\n");
3232 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3237 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3238 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3241 __PACKAGE__-
>register_method({
3242 name
=> 'vm_resume',
3243 path
=> '{vmid}/status/resume',
3247 description
=> "Resume virtual machine.",
3249 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3252 additionalProperties
=> 0,
3254 node
=> get_standard_option
('pve-node'),
3255 vmid
=> get_standard_option
('pve-vmid',
3256 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3257 skiplock
=> get_standard_option
('skiplock'),
3258 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3268 my $rpcenv = PVE
::RPCEnvironment
::get
();
3270 my $authuser = $rpcenv->get_user();
3272 my $node = extract_param
($param, 'node');
3274 my $vmid = extract_param
($param, 'vmid');
3276 my $skiplock = extract_param
($param, 'skiplock');
3277 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3278 if $skiplock && $authuser ne 'root@pam';
3280 # nocheck is used as part of migration when config file might be still
3282 my $nocheck = extract_param
($param, 'nocheck');
3283 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3284 if $nocheck && $authuser ne 'root@pam';
3286 my $to_disk_suspended;
3288 PVE
::QemuConfig-
>lock_config($vmid, sub {
3289 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3290 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3294 die "VM $vmid not running\n"
3295 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3300 syslog
('info', "resume VM $vmid: $upid\n");
3302 if (!$to_disk_suspended) {
3303 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3305 my $storecfg = PVE
::Storage
::config
();
3306 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3312 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3315 __PACKAGE__-
>register_method({
3316 name
=> 'vm_sendkey',
3317 path
=> '{vmid}/sendkey',
3321 description
=> "Send key event to virtual machine.",
3323 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3326 additionalProperties
=> 0,
3328 node
=> get_standard_option
('pve-node'),
3329 vmid
=> get_standard_option
('pve-vmid',
3330 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3331 skiplock
=> get_standard_option
('skiplock'),
3333 description
=> "The key (qemu monitor encoding).",
3338 returns
=> { type
=> 'null'},
3342 my $rpcenv = PVE
::RPCEnvironment
::get
();
3344 my $authuser = $rpcenv->get_user();
3346 my $node = extract_param
($param, 'node');
3348 my $vmid = extract_param
($param, 'vmid');
3350 my $skiplock = extract_param
($param, 'skiplock');
3351 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3352 if $skiplock && $authuser ne 'root@pam';
3354 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3359 __PACKAGE__-
>register_method({
3360 name
=> 'vm_feature',
3361 path
=> '{vmid}/feature',
3365 description
=> "Check if feature for virtual machine is available.",
3367 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3370 additionalProperties
=> 0,
3372 node
=> get_standard_option
('pve-node'),
3373 vmid
=> get_standard_option
('pve-vmid'),
3375 description
=> "Feature to check.",
3377 enum
=> [ 'snapshot', 'clone', 'copy' ],
3379 snapname
=> get_standard_option
('pve-snapshot-name', {
3387 hasFeature
=> { type
=> 'boolean' },
3390 items
=> { type
=> 'string' },
3397 my $node = extract_param
($param, 'node');
3399 my $vmid = extract_param
($param, 'vmid');
3401 my $snapname = extract_param
($param, 'snapname');
3403 my $feature = extract_param
($param, 'feature');
3405 my $running = PVE
::QemuServer
::check_running
($vmid);
3407 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3410 my $snap = $conf->{snapshots
}->{$snapname};
3411 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3414 my $storecfg = PVE
::Storage
::config
();
3416 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3417 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3420 hasFeature
=> $hasFeature,
3421 nodes
=> [ keys %$nodelist ],
3425 __PACKAGE__-
>register_method({
3427 path
=> '{vmid}/clone',
3431 description
=> "Create a copy of virtual machine/template.",
3433 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3434 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3435 "'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet",
3438 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3440 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3441 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3446 additionalProperties
=> 0,
3448 node
=> get_standard_option
('pve-node'),
3449 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3450 newid
=> get_standard_option
('pve-vmid', {
3451 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3452 description
=> 'VMID for the clone.' }),
3455 type
=> 'string', format
=> 'dns-name',
3456 description
=> "Set a name for the new VM.",
3461 description
=> "Description for the new VM.",
3465 type
=> 'string', format
=> 'pve-poolid',
3466 description
=> "Add the new VM to the specified pool.",
3468 snapname
=> get_standard_option
('pve-snapshot-name', {
3471 storage
=> get_standard_option
('pve-storage-id', {
3472 description
=> "Target storage for full clone.",
3476 description
=> "Target format for file storage. Only valid for full clone.",
3479 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3484 description
=> "Create a full copy of all disks. This is always done when " .
3485 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3487 target
=> get_standard_option
('pve-node', {
3488 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3492 description
=> "Override I/O bandwidth limit (in KiB/s).",
3496 default => 'clone limit from datacenter or storage config',
3506 my $rpcenv = PVE
::RPCEnvironment
::get
();
3507 my $authuser = $rpcenv->get_user();
3509 my $node = extract_param
($param, 'node');
3510 my $vmid = extract_param
($param, 'vmid');
3511 my $newid = extract_param
($param, 'newid');
3512 my $pool = extract_param
($param, 'pool');
3514 my $snapname = extract_param
($param, 'snapname');
3515 my $storage = extract_param
($param, 'storage');
3516 my $format = extract_param
($param, 'format');
3517 my $target = extract_param
($param, 'target');
3519 my $localnode = PVE
::INotify
::nodename
();
3521 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3525 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3527 my $load_and_check = sub {
3528 $rpcenv->check_pool_exist($pool) if defined($pool);
3529 PVE
::Cluster
::check_node_exists
($target) if $target;
3531 my $storecfg = PVE
::Storage
::config
();
3534 # check if storage is enabled on local node
3535 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3537 # check if storage is available on target node
3538 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3539 # clone only works if target storage is shared
3540 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3541 die "can't clone to non-shared storage '$storage'\n"
3542 if !$scfg->{shared
};
3546 PVE
::Cluster
::check_cfs_quorum
();
3548 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3549 PVE
::QemuConfig-
>check_lock($conf);
3551 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3552 die "unexpected state change\n" if $verify_running != $running;
3554 die "snapshot '$snapname' does not exist\n"
3555 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3557 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3559 die "parameter 'storage' not allowed for linked clones\n"
3560 if defined($storage) && !$full;
3562 die "parameter 'format' not allowed for linked clones\n"
3563 if defined($format) && !$full;
3565 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3567 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3568 PVE
::QemuServer
::check_mapping_access
($rpcenv, $authuser, $oldconf);
3570 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $oldconf);
3572 die "can't clone VM to node '$target' (VM uses local storage)\n"
3573 if $target && !$sharedvm;
3575 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3576 die "unable to create VM $newid: config file already exists\n"
3579 my $newconf = { lock => 'clone' };
3584 foreach my $opt (keys %$oldconf) {
3585 my $value = $oldconf->{$opt};
3587 # do not copy snapshot related info
3588 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3589 $opt eq 'vmstate' || $opt eq 'snapstate';
3591 # no need to copy unused images, because VMID(owner) changes anyways
3592 next if $opt =~ m/^unused\d+$/;
3594 die "cannot clone TPM state while VM is running\n"
3595 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3597 # always change MAC! address
3598 if ($opt =~ m/^net(\d+)$/) {
3599 my $net = PVE
::QemuServer
::parse_net
($value);
3600 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3601 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3602 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3603 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3604 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3605 die "unable to parse drive options for '$opt'\n" if !$drive;
3606 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3607 $newconf->{$opt} = $value; # simply copy configuration
3609 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3610 die "Full clone feature is not supported for drive '$opt'\n"
3611 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3612 $fullclone->{$opt} = 1;
3614 # not full means clone instead of copy
3615 die "Linked clone feature is not supported for drive '$opt'\n"
3616 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3618 $drives->{$opt} = $drive;
3619 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3620 push @$vollist, $drive->{file
};
3623 # copy everything else
3624 $newconf->{$opt} = $value;
3628 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3632 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3633 my $storecfg = PVE
::Storage
::config
();
3635 # auto generate a new uuid
3636 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3637 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3638 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3639 # auto generate a new vmgenid only if the option was set for template
3640 if ($newconf->{vmgenid
}) {
3641 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3644 delete $newconf->{template
};
3646 if ($param->{name
}) {
3647 $newconf->{name
} = $param->{name
};
3649 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3652 if ($param->{description
}) {
3653 $newconf->{description
} = $param->{description
};
3656 # create empty/temp config - this fails if VM already exists on other node
3657 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3658 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3660 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3662 my $newvollist = [];
3669 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3671 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3673 my $bwlimit = extract_param
($param, 'bwlimit');
3675 my $total_jobs = scalar(keys %{$drives});
3678 foreach my $opt (sort keys %$drives) {
3679 my $drive = $drives->{$opt};
3680 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3681 my $completion = $skipcomplete ?
'skip' : 'complete';
3683 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3684 my $storage_list = [ $src_sid ];
3685 push @$storage_list, $storage if defined($storage);
3686 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3690 running
=> $running,
3693 snapname
=> $snapname,
3699 storage
=> $storage,
3703 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3704 if $opt eq 'efidisk0';
3706 my $newdrive = PVE
::QemuServer
::clone_disk
(
3718 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3720 PVE
::QemuConfig-
>write_config($newid, $newconf);
3724 delete $newconf->{lock};
3726 # do not write pending changes
3727 if (my @changes = keys %{$newconf->{pending
}}) {
3728 my $pending = join(',', @changes);
3729 warn "found pending changes for '$pending', discarding for clone\n";
3730 delete $newconf->{pending
};
3733 PVE
::QemuConfig-
>write_config($newid, $newconf);
3736 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3737 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3738 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3740 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3741 die "Failed to move config to node '$target' - rename failed: $!\n"
3742 if !rename($conffile, $newconffile);
3745 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3748 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3749 sleep 1; # some storage like rbd need to wait before release volume - really?
3751 foreach my $volid (@$newvollist) {
3752 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3756 PVE
::Firewall
::remove_vmfw_conf
($newid);
3758 unlink $conffile; # avoid races -> last thing before die
3760 die "clone failed: $err";
3766 # Aquire exclusive lock lock for $newid
3767 my $lock_target_vm = sub {
3768 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3771 my $lock_source_vm = sub {
3772 # exclusive lock if VM is running - else shared lock is enough;
3774 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3776 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3780 $load_and_check->(); # early checks before forking/locking
3782 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3785 __PACKAGE__-
>register_method({
3786 name
=> 'move_vm_disk',
3787 path
=> '{vmid}/move_disk',
3791 description
=> "Move volume to different storage or to a different VM.",
3793 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3794 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3795 "a disk to another VM, you need the permissions on the target VM as well.",
3796 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3799 additionalProperties
=> 0,
3801 node
=> get_standard_option
('pve-node'),
3802 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3803 'target-vmid' => get_standard_option
('pve-vmid', {
3804 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3809 description
=> "The disk you want to move.",
3810 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3812 storage
=> get_standard_option
('pve-storage-id', {
3813 description
=> "Target storage.",
3814 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3819 description
=> "Target Format.",
3820 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3825 description
=> "Delete the original disk after successful copy. By default the"
3826 ." original disk is kept as unused disk.",
3832 description
=> 'Prevent changes if current configuration file has different SHA1"
3833 ." digest. This can be used to prevent concurrent modifications.',
3838 description
=> "Override I/O bandwidth limit (in KiB/s).",
3842 default => 'move limit from datacenter or storage config',
3846 description
=> "The config key the disk will be moved to on the target VM"
3847 ." (for example, ide0 or scsi1). Default is the source disk key.",
3848 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3851 'target-digest' => {
3853 description
=> 'Prevent changes if the current config file of the target VM has a"
3854 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3862 description
=> "the task ID.",
3867 my $rpcenv = PVE
::RPCEnvironment
::get
();
3868 my $authuser = $rpcenv->get_user();
3870 my $node = extract_param
($param, 'node');
3871 my $vmid = extract_param
($param, 'vmid');
3872 my $target_vmid = extract_param
($param, 'target-vmid');
3873 my $digest = extract_param
($param, 'digest');
3874 my $target_digest = extract_param
($param, 'target-digest');
3875 my $disk = extract_param
($param, 'disk');
3876 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3877 my $storeid = extract_param
($param, 'storage');
3878 my $format = extract_param
($param, 'format');
3880 my $storecfg = PVE
::Storage
::config
();
3882 my $load_and_check_move = sub {
3883 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3884 PVE
::QemuConfig-
>check_lock($conf);
3886 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3888 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3890 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3892 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3893 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3895 my $old_volid = $drive->{file
};
3897 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3898 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3902 die "you can't move to the same storage with same format\n"
3903 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3905 # this only checks snapshots because $disk is passed!
3906 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3912 die "you can't move a disk with snapshots and delete the source\n"
3913 if $snapshotted && $param->{delete};
3915 return ($conf, $drive, $oldstoreid, $snapshotted);
3918 my $move_updatefn = sub {
3919 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3920 my $old_volid = $drive->{file
};
3922 PVE
::Cluster
::log_msg
(
3925 "move disk VM $vmid: move --disk $disk --storage $storeid"
3928 my $running = PVE
::QemuServer
::check_running
($vmid);
3930 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3932 my $newvollist = [];
3938 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3940 warn "moving disk with snapshots, snapshots will not be moved!\n"
3943 my $bwlimit = extract_param
($param, 'bwlimit');
3944 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3946 [$oldstoreid, $storeid],
3952 running
=> $running,
3961 storage
=> $storeid,
3965 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3966 if $disk eq 'efidisk0';
3968 my $newdrive = PVE
::QemuServer
::clone_disk
(
3979 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3981 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3983 # convert moved disk to base if part of template
3984 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3985 if PVE
::QemuConfig-
>is_template($conf);
3987 PVE
::QemuConfig-
>write_config($vmid, $conf);
3989 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3990 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3991 eval { mon_cmd
($vmid, "guest-fstrim") };
3995 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3996 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
4002 foreach my $volid (@$newvollist) {
4003 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
4006 die "storage migration failed: $err";
4009 if ($param->{delete}) {
4011 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
4012 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
4018 my $load_and_check_reassign_configs = sub {
4019 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
4021 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
4022 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
4024 my $source_node = $vmlist->{$vmid}->{node
};
4025 my $target_node = $vmlist->{$target_vmid}->{node
};
4027 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
4028 if $source_node ne $target_node;
4030 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
4031 PVE
::QemuConfig-
>check_lock($source_conf);
4032 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
4033 PVE
::QemuConfig-
>check_lock($target_conf);
4035 die "Can't move disks from or to template VMs\n"
4036 if ($source_conf->{template
} || $target_conf->{template
});
4039 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
4040 die "VM ${vmid}: $@" if $@;
4043 if ($target_digest) {
4044 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
4045 die "VM ${target_vmid}: $@" if $@;
4048 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
4050 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
4051 if $target_conf->{$target_disk};
4053 my $drive = PVE
::QemuServer
::parse_drive
(
4055 $source_conf->{$disk},
4057 die "failed to parse source disk - $@\n" if !$drive;
4059 my $source_volid = $drive->{file
};
4061 die "disk '${disk}' has no associated volume\n" if !$source_volid;
4062 die "CD drive contents can't be moved to another VM\n"
4063 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
4065 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
4066 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
4068 die "Can't move disk used by a snapshot to another VM\n"
4069 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
4070 die "Storage does not support moving of this disk to another VM\n"
4071 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
4072 die "Cannot move disk to another VM while the source VM is running - detach first\n"
4073 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
4075 # now re-parse using target disk slot format
4076 if ($target_disk =~ /^unused\d+$/) {
4077 $drive = PVE
::QemuServer
::parse_drive
(
4082 $drive = PVE
::QemuServer
::parse_drive
(
4084 $source_conf->{$disk},
4087 die "failed to parse source disk for target disk format - $@\n" if !$drive;
4089 my $repl_conf = PVE
::ReplicationConfig-
>new();
4090 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
4091 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4092 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
4093 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
4096 return ($source_conf, $target_conf, $drive);
4101 print STDERR
"$msg\n";
4104 my $disk_reassignfn = sub {
4105 return PVE
::QemuConfig-
>lock_config($vmid, sub {
4106 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
4107 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
4109 my $source_volid = $drive->{file
};
4111 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
4112 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
4114 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4116 my $new_volid = PVE
::Storage
::rename_volume
(
4122 $drive->{file
} = $new_volid;
4124 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
4125 if (defined(delete $boot_order->{$disk})) {
4126 print "removing disk '$disk' from boot order config\n";
4127 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
4128 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
4131 delete $source_conf->{$disk};
4132 print "removing disk '${disk}' from VM '${vmid}' config\n";
4133 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
4135 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
4137 if ($target_disk =~ /^unused\d+$/) {
4138 $target_conf->{$target_disk} = $drive_string;
4139 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4144 vmid
=> $target_vmid,
4145 digest
=> $target_digest,
4146 $target_disk => $drive_string,
4152 # remove possible replication snapshots
4153 if (PVE
::Storage
::volume_has_feature
(
4159 PVE
::Replication
::prepare
(
4169 print "Failed to remove replication snapshots on moved disk " .
4170 "'$target_disk'. Manual cleanup could be necessary.\n";
4177 if ($target_vmid && $storeid) {
4178 my $msg = "either set 'storage' or 'target-vmid', but not both";
4179 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4180 } elsif ($target_vmid) {
4181 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4182 if $authuser ne 'root@pam';
4184 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4185 if $vmid eq $target_vmid;
4187 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4188 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4189 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4191 return $rpcenv->fork_worker(
4193 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4197 } elsif ($storeid) {
4198 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4200 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4201 if $disk =~ m/^unused\d+$/;
4203 $load_and_check_move->(); # early checks before forking/locking
4206 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4209 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4211 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4212 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4216 my $check_vm_disks_local = sub {
4217 my ($storecfg, $vmconf, $vmid) = @_;
4219 my $local_disks = {};
4221 # add some more information to the disks e.g. cdrom
4222 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4223 my ($volid, $attr) = @_;
4225 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4227 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4228 return if $scfg->{shared
};
4230 # The shared attr here is just a special case where the vdisk
4231 # is marked as shared manually
4232 return if $attr->{shared
};
4233 return if $attr->{cdrom
} and $volid eq "none";
4235 if (exists $local_disks->{$volid}) {
4236 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4238 $local_disks->{$volid} = $attr;
4239 # ensure volid is present in case it's needed
4240 $local_disks->{$volid}->{volid
} = $volid;
4244 return $local_disks;
4247 __PACKAGE__-
>register_method({
4248 name
=> 'migrate_vm_precondition',
4249 path
=> '{vmid}/migrate',
4253 description
=> "Get preconditions for migration.",
4255 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4258 additionalProperties
=> 0,
4260 node
=> get_standard_option
('pve-node'),
4261 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4262 target
=> get_standard_option
('pve-node', {
4263 description
=> "Target node.",
4264 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4272 running
=> { type
=> 'boolean' },
4276 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4278 not_allowed_nodes
=> {
4281 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4285 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4287 local_resources
=> {
4289 description
=> "List local resources e.g. pci, usb"
4296 my $rpcenv = PVE
::RPCEnvironment
::get
();
4298 my $authuser = $rpcenv->get_user();
4300 PVE
::Cluster
::check_cfs_quorum
();
4304 my $vmid = extract_param
($param, 'vmid');
4305 my $target = extract_param
($param, 'target');
4306 my $localnode = PVE
::INotify
::nodename
();
4310 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4311 my $storecfg = PVE
::Storage
::config
();
4314 # try to detect errors early
4315 PVE
::QemuConfig-
>check_lock($vmconf);
4317 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4319 # if vm is not running, return target nodes where local storage is available
4320 # for offline migration
4321 if (!$res->{running
}) {
4322 $res->{allowed_nodes
} = [];
4323 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4324 delete $checked_nodes->{$localnode};
4326 foreach my $node (keys %$checked_nodes) {
4327 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
4328 push @{$res->{allowed_nodes
}}, $node;
4332 $res->{not_allowed_nodes
} = $checked_nodes;
4336 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4337 $res->{local_disks
} = [ values %$local_disks ];;
4339 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4341 $res->{local_resources
} = $local_resources;
4348 __PACKAGE__-
>register_method({
4349 name
=> 'migrate_vm',
4350 path
=> '{vmid}/migrate',
4354 description
=> "Migrate virtual machine. Creates a new migration task.",
4356 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4359 additionalProperties
=> 0,
4361 node
=> get_standard_option
('pve-node'),
4362 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4363 target
=> get_standard_option
('pve-node', {
4364 description
=> "Target node.",
4365 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4369 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4374 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4379 enum
=> ['secure', 'insecure'],
4380 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4383 migration_network
=> {
4384 type
=> 'string', format
=> 'CIDR',
4385 description
=> "CIDR of the (sub) network that is used for migration.",
4388 "with-local-disks" => {
4390 description
=> "Enable live storage migration for local disk",
4393 targetstorage
=> get_standard_option
('pve-targetstorage', {
4394 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4397 description
=> "Override I/O bandwidth limit (in KiB/s).",
4401 default => 'migrate limit from datacenter or storage config',
4407 description
=> "the task ID.",
4412 my $rpcenv = PVE
::RPCEnvironment
::get
();
4413 my $authuser = $rpcenv->get_user();
4415 my $target = extract_param
($param, 'target');
4417 my $localnode = PVE
::INotify
::nodename
();
4418 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4420 PVE
::Cluster
::check_cfs_quorum
();
4422 PVE
::Cluster
::check_node_exists
($target);
4424 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4426 my $vmid = extract_param
($param, 'vmid');
4428 raise_param_exc
({ force
=> "Only root may use this option." })
4429 if $param->{force
} && $authuser ne 'root@pam';
4431 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4432 if $param->{migration_type
} && $authuser ne 'root@pam';
4434 # allow root only until better network permissions are available
4435 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4436 if $param->{migration_network
} && $authuser ne 'root@pam';
4439 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4441 # try to detect errors early
4443 PVE
::QemuConfig-
>check_lock($conf);
4445 if (PVE
::QemuServer
::check_running
($vmid)) {
4446 die "can't migrate running VM without --online\n" if !$param->{online
};
4448 my $repl_conf = PVE
::ReplicationConfig-
>new();
4449 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4450 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4451 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4452 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4453 "target. Use 'force' to override.\n";
4456 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4457 $param->{online
} = 0;
4460 my $storecfg = PVE
::Storage
::config
();
4461 if (my $targetstorage = $param->{targetstorage
}) {
4462 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4463 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4466 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4467 if !defined($storagemap->{identity
});
4469 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4470 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4473 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4474 if $storagemap->{default};
4476 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4477 if $storagemap->{identity
};
4479 $param->{storagemap
} = $storagemap;
4481 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4484 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4489 print "Requesting HA migration for VM $vmid to node $target\n";
4491 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4492 PVE
::Tools
::run_command
($cmd);
4496 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4501 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4505 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4508 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4513 __PACKAGE__-
>register_method({
4514 name
=> 'remote_migrate_vm',
4515 path
=> '{vmid}/remote_migrate',
4519 description
=> "Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!",
4521 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4524 additionalProperties
=> 0,
4526 node
=> get_standard_option
('pve-node'),
4527 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4528 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
4529 'target-endpoint' => get_standard_option
('proxmox-remote', {
4530 description
=> "Remote target endpoint",
4534 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4539 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.",
4543 'target-storage' => get_standard_option
('pve-targetstorage', {
4544 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4547 'target-bridge' => {
4549 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.",
4550 format
=> 'bridge-pair-list',
4553 description
=> "Override I/O bandwidth limit (in KiB/s).",
4557 default => 'migrate limit from datacenter or storage config',
4563 description
=> "the task ID.",
4568 my $rpcenv = PVE
::RPCEnvironment
::get
();
4569 my $authuser = $rpcenv->get_user();
4571 my $source_vmid = extract_param
($param, 'vmid');
4572 my $target_endpoint = extract_param
($param, 'target-endpoint');
4573 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
4575 my $delete = extract_param
($param, 'delete') // 0;
4577 PVE
::Cluster
::check_cfs_quorum
();
4580 my $conf = PVE
::QemuConfig-
>load_config($source_vmid);
4582 PVE
::QemuConfig-
>check_lock($conf);
4584 raise_param_exc
({ vmid
=> "cannot migrate HA-managed VM to remote cluster" })
4585 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
4587 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
4589 # TODO: move this as helper somewhere appropriate?
4591 protocol
=> 'https',
4592 host
=> $remote->{host
},
4593 port
=> $remote->{port
} // 8006,
4594 apitoken
=> $remote->{apitoken
},
4598 if ($fp = $remote->{fingerprint
}) {
4599 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
4602 print "Establishing API connection with remote at '$remote->{host}'\n";
4604 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
4606 if (!defined($fp)) {
4607 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
4608 foreach my $cert (@$cert_info) {
4609 my $filename = $cert->{filename
};
4610 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
4611 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
4613 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
4617 my $repl_conf = PVE
::ReplicationConfig-
>new();
4618 my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1);
4619 die "cannot remote-migrate replicated VM\n" if $is_replicated;
4621 if (PVE
::QemuServer
::check_running
($source_vmid)) {
4622 die "can't migrate running VM without --online\n" if !$param->{online
};
4625 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4626 $param->{online
} = 0;
4629 my $storecfg = PVE
::Storage
::config
();
4630 my $target_storage = extract_param
($param, 'target-storage');
4631 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
4632 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
4635 my $target_bridge = extract_param
($param, 'target-bridge');
4636 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
4637 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
4640 die "remote migration requires explicit storage mapping!\n"
4641 if $storagemap->{identity
};
4643 $param->{storagemap
} = $storagemap;
4644 $param->{bridgemap
} = $bridgemap;
4645 $param->{remote
} = {
4646 conn
=> $conn_args, # re-use fingerprint for tunnel
4647 client
=> $api_client,
4648 vmid
=> $target_vmid,
4650 $param->{migration_type
} = 'websocket';
4651 $param->{'with-local-disks'} = 1;
4652 $param->{delete} = $delete if $delete;
4654 my $cluster_status = $api_client->get("/cluster/status");
4656 foreach my $entry (@$cluster_status) {
4657 next if $entry->{type
} ne 'node';
4658 if ($entry->{local}) {
4659 $target_node = $entry->{name
};
4664 die "couldn't determine endpoint's node name\n"
4665 if !defined($target_node);
4668 PVE
::QemuMigrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
4672 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
4675 return $rpcenv->fork_worker('qmigrate', $source_vmid, $authuser, $worker);
4678 __PACKAGE__-
>register_method({
4680 path
=> '{vmid}/monitor',
4684 description
=> "Execute QEMU monitor commands.",
4686 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4687 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4690 additionalProperties
=> 0,
4692 node
=> get_standard_option
('pve-node'),
4693 vmid
=> get_standard_option
('pve-vmid'),
4696 description
=> "The monitor command.",
4700 returns
=> { type
=> 'string'},
4704 my $rpcenv = PVE
::RPCEnvironment
::get
();
4705 my $authuser = $rpcenv->get_user();
4708 my $command = shift;
4709 return $command =~ m/^\s*info(\s+|$)/
4710 || $command =~ m/^\s*help\s*$/;
4713 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4714 if !&$is_ro($param->{command
});
4716 my $vmid = $param->{vmid
};
4718 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4722 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4724 $res = "ERROR: $@" if $@;
4729 __PACKAGE__-
>register_method({
4730 name
=> 'resize_vm',
4731 path
=> '{vmid}/resize',
4735 description
=> "Extend volume size.",
4737 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4740 additionalProperties
=> 0,
4742 node
=> get_standard_option
('pve-node'),
4743 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4744 skiplock
=> get_standard_option
('skiplock'),
4747 description
=> "The disk you want to resize.",
4748 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4752 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4753 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.",
4757 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4765 description
=> "the task ID.",
4770 my $rpcenv = PVE
::RPCEnvironment
::get
();
4772 my $authuser = $rpcenv->get_user();
4774 my $node = extract_param
($param, 'node');
4776 my $vmid = extract_param
($param, 'vmid');
4778 my $digest = extract_param
($param, 'digest');
4780 my $disk = extract_param
($param, 'disk');
4782 my $sizestr = extract_param
($param, 'size');
4784 my $skiplock = extract_param
($param, 'skiplock');
4785 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4786 if $skiplock && $authuser ne 'root@pam';
4788 my $storecfg = PVE
::Storage
::config
();
4790 my $updatefn = sub {
4792 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4794 die "checksum missmatch (file change by other user?)\n"
4795 if $digest && $digest ne $conf->{digest
};
4796 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4798 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4800 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4802 my (undef, undef, undef, undef, undef, undef, $format) =
4803 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4805 my $volid = $drive->{file
};
4807 die "disk '$disk' has no associated volume\n" if !$volid;
4809 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4811 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4813 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4815 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4816 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4818 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4820 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4821 my ($ext, $newsize, $unit) = ($1, $2, $4);
4824 $newsize = $newsize * 1024;
4825 } elsif ($unit eq 'M') {
4826 $newsize = $newsize * 1024 * 1024;
4827 } elsif ($unit eq 'G') {
4828 $newsize = $newsize * 1024 * 1024 * 1024;
4829 } elsif ($unit eq 'T') {
4830 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4833 $newsize += $size if $ext;
4834 $newsize = int($newsize);
4836 die "shrinking disks is not supported\n" if $newsize < $size;
4838 return if $size == $newsize;
4840 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4842 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4844 $drive->{size
} = $newsize;
4845 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4847 PVE
::QemuConfig-
>write_config($vmid, $conf);
4851 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4854 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
4857 __PACKAGE__-
>register_method({
4858 name
=> 'snapshot_list',
4859 path
=> '{vmid}/snapshot',
4861 description
=> "List all snapshots.",
4863 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4866 protected
=> 1, # qemu pid files are only readable by root
4868 additionalProperties
=> 0,
4870 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4871 node
=> get_standard_option
('pve-node'),
4880 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4884 description
=> "Snapshot includes RAM.",
4889 description
=> "Snapshot description.",
4893 description
=> "Snapshot creation time",
4895 renderer
=> 'timestamp',
4899 description
=> "Parent snapshot identifier.",
4905 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4910 my $vmid = $param->{vmid
};
4912 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4913 my $snaphash = $conf->{snapshots
} || {};
4917 foreach my $name (keys %$snaphash) {
4918 my $d = $snaphash->{$name};
4921 snaptime
=> $d->{snaptime
} || 0,
4922 vmstate
=> $d->{vmstate
} ?
1 : 0,
4923 description
=> $d->{description
} || '',
4925 $item->{parent
} = $d->{parent
} if $d->{parent
};
4926 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4930 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4933 digest
=> $conf->{digest
},
4934 running
=> $running,
4935 description
=> "You are here!",
4937 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4939 push @$res, $current;
4944 __PACKAGE__-
>register_method({
4946 path
=> '{vmid}/snapshot',
4950 description
=> "Snapshot a VM.",
4952 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4955 additionalProperties
=> 0,
4957 node
=> get_standard_option
('pve-node'),
4958 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4959 snapname
=> get_standard_option
('pve-snapshot-name'),
4963 description
=> "Save the vmstate",
4968 description
=> "A textual description or comment.",
4974 description
=> "the task ID.",
4979 my $rpcenv = PVE
::RPCEnvironment
::get
();
4981 my $authuser = $rpcenv->get_user();
4983 my $node = extract_param
($param, 'node');
4985 my $vmid = extract_param
($param, 'vmid');
4987 my $snapname = extract_param
($param, 'snapname');
4989 die "unable to use snapshot name 'current' (reserved name)\n"
4990 if $snapname eq 'current';
4992 die "unable to use snapshot name 'pending' (reserved name)\n"
4993 if lc($snapname) eq 'pending';
4996 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4997 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4998 $param->{description
});
5001 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
5004 __PACKAGE__-
>register_method({
5005 name
=> 'snapshot_cmd_idx',
5006 path
=> '{vmid}/snapshot/{snapname}',
5013 additionalProperties
=> 0,
5015 vmid
=> get_standard_option
('pve-vmid'),
5016 node
=> get_standard_option
('pve-node'),
5017 snapname
=> get_standard_option
('pve-snapshot-name'),
5026 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
5033 push @$res, { cmd
=> 'rollback' };
5034 push @$res, { cmd
=> 'config' };
5039 __PACKAGE__-
>register_method({
5040 name
=> 'update_snapshot_config',
5041 path
=> '{vmid}/snapshot/{snapname}/config',
5045 description
=> "Update snapshot metadata.",
5047 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5050 additionalProperties
=> 0,
5052 node
=> get_standard_option
('pve-node'),
5053 vmid
=> get_standard_option
('pve-vmid'),
5054 snapname
=> get_standard_option
('pve-snapshot-name'),
5058 description
=> "A textual description or comment.",
5062 returns
=> { type
=> 'null' },
5066 my $rpcenv = PVE
::RPCEnvironment
::get
();
5068 my $authuser = $rpcenv->get_user();
5070 my $vmid = extract_param
($param, 'vmid');
5072 my $snapname = extract_param
($param, 'snapname');
5074 return if !defined($param->{description
});
5076 my $updatefn = sub {
5078 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5080 PVE
::QemuConfig-
>check_lock($conf);
5082 my $snap = $conf->{snapshots
}->{$snapname};
5084 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5086 $snap->{description
} = $param->{description
} if defined($param->{description
});
5088 PVE
::QemuConfig-
>write_config($vmid, $conf);
5091 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
5096 __PACKAGE__-
>register_method({
5097 name
=> 'get_snapshot_config',
5098 path
=> '{vmid}/snapshot/{snapname}/config',
5101 description
=> "Get snapshot configuration",
5103 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
5106 additionalProperties
=> 0,
5108 node
=> get_standard_option
('pve-node'),
5109 vmid
=> get_standard_option
('pve-vmid'),
5110 snapname
=> get_standard_option
('pve-snapshot-name'),
5113 returns
=> { type
=> "object" },
5117 my $rpcenv = PVE
::RPCEnvironment
::get
();
5119 my $authuser = $rpcenv->get_user();
5121 my $vmid = extract_param
($param, 'vmid');
5123 my $snapname = extract_param
($param, 'snapname');
5125 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5127 my $snap = $conf->{snapshots
}->{$snapname};
5129 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5134 __PACKAGE__-
>register_method({
5136 path
=> '{vmid}/snapshot/{snapname}/rollback',
5140 description
=> "Rollback VM state to specified snapshot.",
5142 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
5145 additionalProperties
=> 0,
5147 node
=> get_standard_option
('pve-node'),
5148 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5149 snapname
=> get_standard_option
('pve-snapshot-name'),
5152 description
=> "Whether the VM should get started after rolling back successfully."
5153 . " (Note: VMs will be automatically started if the snapshot includes RAM.)",
5161 description
=> "the task ID.",
5166 my $rpcenv = PVE
::RPCEnvironment
::get
();
5168 my $authuser = $rpcenv->get_user();
5170 my $node = extract_param
($param, 'node');
5172 my $vmid = extract_param
($param, 'vmid');
5174 my $snapname = extract_param
($param, 'snapname');
5177 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
5178 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
5180 if ($param->{start
} && !PVE
::QemuServer
::Helpers
::vm_running_locally
($vmid)) {
5181 PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node });
5186 # hold migration lock, this makes sure that nobody create replication snapshots
5187 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
5190 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
5193 __PACKAGE__-
>register_method({
5194 name
=> 'delsnapshot',
5195 path
=> '{vmid}/snapshot/{snapname}',
5199 description
=> "Delete a VM snapshot.",
5201 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5204 additionalProperties
=> 0,
5206 node
=> get_standard_option
('pve-node'),
5207 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5208 snapname
=> get_standard_option
('pve-snapshot-name'),
5212 description
=> "For removal from config file, even if removing disk snapshots fails.",
5218 description
=> "the task ID.",
5223 my $rpcenv = PVE
::RPCEnvironment
::get
();
5225 my $authuser = $rpcenv->get_user();
5227 my $node = extract_param
($param, 'node');
5229 my $vmid = extract_param
($param, 'vmid');
5231 my $snapname = extract_param
($param, 'snapname');
5234 my $do_delete = sub {
5236 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
5237 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
5241 if ($param->{force
}) {
5244 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
5246 die $err if $lock_obtained;
5247 die "Failed to obtain guest migration lock - replication running?\n";
5252 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
5255 __PACKAGE__-
>register_method({
5257 path
=> '{vmid}/template',
5261 description
=> "Create a Template.",
5263 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
5264 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
5267 additionalProperties
=> 0,
5269 node
=> get_standard_option
('pve-node'),
5270 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
5274 description
=> "If you want to convert only 1 disk to base image.",
5275 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
5282 description
=> "the task ID.",
5287 my $rpcenv = PVE
::RPCEnvironment
::get
();
5289 my $authuser = $rpcenv->get_user();
5291 my $node = extract_param
($param, 'node');
5293 my $vmid = extract_param
($param, 'vmid');
5295 my $disk = extract_param
($param, 'disk');
5297 my $load_and_check = sub {
5298 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5300 PVE
::QemuConfig-
>check_lock($conf);
5302 die "unable to create template, because VM contains snapshots\n"
5303 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
5305 die "you can't convert a template to a template\n"
5306 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
5308 die "you can't convert a VM to template if VM is running\n"
5309 if PVE
::QemuServer
::check_running
($vmid);
5314 $load_and_check->();
5317 PVE
::QemuConfig-
>lock_config($vmid, sub {
5318 my $conf = $load_and_check->();
5320 $conf->{template
} = 1;
5321 PVE
::QemuConfig-
>write_config($vmid, $conf);
5323 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5327 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5330 __PACKAGE__-
>register_method({
5331 name
=> 'cloudinit_generated_config_dump',
5332 path
=> '{vmid}/cloudinit/dump',
5335 description
=> "Get automatically generated cloudinit config.",
5337 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5340 additionalProperties
=> 0,
5342 node
=> get_standard_option
('pve-node'),
5343 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5345 description
=> 'Config type.',
5347 enum
=> ['user', 'network', 'meta'],
5357 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5359 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});
5362 __PACKAGE__-
>register_method({
5364 path
=> '{vmid}/mtunnel',
5367 description
=> 'Migration tunnel endpoint - only for internal use by VM migration.',
5371 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
5372 ['perm', '/', [ 'Sys.Incoming' ]],
5374 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
5375 " on '/'. Further permission checks happen during the actual migration.",
5378 additionalProperties
=> 0,
5380 node
=> get_standard_option
('pve-node'),
5381 vmid
=> get_standard_option
('pve-vmid'),
5384 format
=> 'pve-storage-id-list',
5386 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
5390 format
=> 'pve-bridge-id-list',
5392 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
5397 additionalProperties
=> 0,
5399 upid
=> { type
=> 'string' },
5400 ticket
=> { type
=> 'string' },
5401 socket => { type
=> 'string' },
5407 my $rpcenv = PVE
::RPCEnvironment
::get
();
5408 my $authuser = $rpcenv->get_user();
5410 my $node = extract_param
($param, 'node');
5411 my $vmid = extract_param
($param, 'vmid');
5413 my $storages = extract_param
($param, 'storages');
5414 my $bridges = extract_param
($param, 'bridges');
5416 my $nodename = PVE
::INotify
::nodename
();
5418 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5419 if $node ne 'localhost' && $node ne $nodename;
5423 my $storecfg = PVE
::Storage
::config
();
5424 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
5425 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
5428 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
5429 PVE
::Network
::read_bridge_mtu
($bridge);
5432 PVE
::Cluster
::check_cfs_quorum
();
5434 my $lock = 'create';
5435 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, 0, $lock); };
5437 raise_param_exc
({ vmid
=> "unable to create empty VM config - $@"})
5442 storecfg
=> PVE
::Storage
::config
(),
5447 my $run_locked = sub {
5448 my ($code, $params) = @_;
5449 return PVE
::QemuConfig-
>lock_config($state->{vmid
}, sub {
5450 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5452 $state->{conf
} = $conf;
5454 die "Encountered wrong lock - aborting mtunnel command handling.\n"
5455 if $state->{lock} && !PVE
::QemuConfig-
>has_lock($conf, $state->{lock});
5457 return $code->($params);
5465 description
=> 'Full VM config, adapted for target cluster/node',
5467 'firewall-config' => {
5469 description
=> 'VM firewall config',
5474 format
=> PVE
::JSONSchema
::get_standard_option
('pve-qm-image-format'),
5477 format
=> 'pve-storage-id',
5481 description
=> 'parsed drive information without volid and format',
5487 description
=> 'params passed to vm_start_nolock',
5491 description
=> 'migrate_opts passed to vm_start_nolock',
5497 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
5503 description
=> 'remove VM config and disks, aborting migration',
5507 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
5508 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
5509 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
5512 my $cmd_handlers = {
5514 # compared against other end's version
5515 # bump/reset for breaking changes
5516 # bump/bump for opt-in changes
5518 api
=> $PVE::QemuMigrate
::WS_TUNNEL_VERSION
,
5525 # parse and write out VM FW config if given
5526 if (my $fw_conf = $params->{'firewall-config'}) {
5527 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
5534 ipset_comments
=> {},
5536 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
5538 # TODO: add flag for strict parsing?
5539 # TODO: add import sub that does all this given raw content?
5540 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
5541 $vmfw_conf->{vmid
} = $state->{vmid
};
5542 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
5544 $state->{cleanup
}->{fw
} = 1;
5547 my $conf_fn = "incoming/qemu-server/$state->{vmid}.conf";
5548 my $new_conf = PVE
::QemuServer
::parse_vm_config
($conf_fn, $params->{conf
}, 1);
5549 delete $new_conf->{lock};
5550 delete $new_conf->{digest
};
5552 # TODO handle properly?
5553 delete $new_conf->{snapshots
};
5554 delete $new_conf->{parent
};
5555 delete $new_conf->{pending
};
5557 # not handled by update_vm_api
5558 my $vmgenid = delete $new_conf->{vmgenid
};
5559 my $meta = delete $new_conf->{meta
};
5560 my $cloudinit = delete $new_conf->{cloudinit
}; # this is informational only
5561 $new_conf->{skip_cloud_init
} = 1; # re-use image from source side
5563 $new_conf->{vmid
} = $state->{vmid
};
5564 $new_conf->{node
} = $node;
5566 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, 'create');
5569 $update_vm_api->($new_conf, 1);
5572 # revert to locked previous config
5573 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5574 $conf->{lock} = 'create';
5575 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5580 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5581 $conf->{lock} = 'migrate';
5582 $conf->{vmgenid
} = $vmgenid if defined($vmgenid);
5583 $conf->{meta
} = $meta if defined($meta);
5584 $conf->{cloudinit
} = $cloudinit if defined($cloudinit);
5585 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5587 $state->{lock} = 'migrate';
5593 return PVE
::StorageTunnel
::handle_bwlimit
($params);
5598 my $format = $params->{format
};
5599 my $storeid = $params->{storage
};
5600 my $drive = $params->{drive
};
5602 $check_storage_access_migrate->($rpcenv, $authuser, $state->{storecfg
}, $storeid, $node);
5605 default => $storeid,
5608 my $source_volumes = {
5619 my $res = PVE
::QemuServer
::vm_migrate_alloc_nbd_disks
($state->{storecfg
}, $state->{vmid
}, $source_volumes, $storagemap);
5620 if (defined($res->{disk
})) {
5621 $state->{cleanup
}->{volumes
}->{$res->{disk
}->{volid
}} = 1;
5622 return $res->{disk
};
5624 die "failed to allocate NBD disk..\n";
5627 'disk-import' => sub {
5630 $check_storage_access_migrate->(
5638 $params->{unix
} = "/run/qemu-server/$state->{vmid}.storage";
5640 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
5642 'query-disk-import' => sub {
5645 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
5650 my $info = PVE
::QemuServer
::vm_start_nolock
(
5654 $params->{start_params
},
5655 $params->{migrate_opts
},
5659 if ($info->{migrate
}->{proto
} ne 'unix') {
5660 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5661 die "migration over non-UNIX sockets not possible\n";
5664 my $socket = $info->{migrate
}->{addr
};
5665 chown $state->{socket_uid
}, -1, $socket;
5666 $state->{sockets
}->{$socket} = 1;
5668 my $unix_sockets = $info->{migrate
}->{unix_sockets
};
5669 foreach my $socket (@$unix_sockets) {
5670 chown $state->{socket_uid
}, -1, $socket;
5671 $state->{sockets
}->{$socket} = 1;
5676 if (PVE
::QemuServer
::qga_check_running
($state->{vmid
})) {
5677 eval { mon_cmd
($state->{vmid
}, "guest-fstrim") };
5678 warn "fstrim failed: $@\n" if $@;
5683 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5687 PVE
::QemuServer
::nbd_stop
($state->{vmid
});
5691 if (PVE
::QemuServer
::Helpers
::vm_running_locally
($state->{vmid
})) {
5692 PVE
::QemuServer
::vm_resume
($state->{vmid
}, 1, 1);
5694 die "VM $state->{vmid} not running\n";
5699 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, $state->{lock});
5700 delete $state->{lock};
5706 my $path = $params->{path
};
5708 die "Not allowed to generate ticket for unknown socket '$path'\n"
5709 if !defined($state->{sockets
}->{$path});
5711 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
5716 if ($params->{cleanup
}) {
5717 if ($state->{cleanup
}->{fw
}) {
5718 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
5721 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
5722 print "freeing volume '$volid' as part of cleanup\n";
5723 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
5727 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5730 print "switching to exit-mode, waiting for client to disconnect\n";
5737 my $socket_addr = "/run/qemu-server/$state->{vmid}.mtunnel";
5738 unlink $socket_addr;
5740 $state->{socket} = IO
::Socket
::UNIX-
>new(
5741 Type
=> SOCK_STREAM
(),
5742 Local
=> $socket_addr,
5746 $state->{socket_uid
} = getpwnam('www-data')
5747 or die "Failed to resolve user 'www-data' to numeric UID\n";
5748 chown $state->{socket_uid
}, -1, $socket_addr;
5751 print "mtunnel started\n";
5753 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
5755 warn "Failed to accept tunnel connection - $@\n";
5757 warn "Removing tunnel socket..\n";
5758 unlink $state->{socket};
5760 warn "Removing temporary VM config..\n";
5762 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5765 die "Exiting mtunnel\n";
5768 $state->{conn
} = $conn;
5770 my $reply_err = sub {
5773 my $reply = JSON
::encode_json
({
5774 success
=> JSON
::false
,
5777 $conn->print("$reply\n");
5781 my $reply_ok = sub {
5784 $res->{success
} = JSON
::true
;
5785 my $reply = JSON
::encode_json
($res);
5786 $conn->print("$reply\n");
5790 while (my $line = <$conn>) {
5793 # untaint, we validate below if needed
5794 ($line) = $line =~ /^(.*)$/;
5795 my $parsed = eval { JSON
::decode_json
($line) };
5797 $reply_err->("failed to parse command - $@");
5801 my $cmd = delete $parsed->{cmd
};
5802 if (!defined($cmd)) {
5803 $reply_err->("'cmd' missing");
5804 } elsif ($state->{exit}) {
5805 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
5807 } elsif (my $handler = $cmd_handlers->{$cmd}) {
5808 print "received command '$cmd'\n";
5810 if ($cmd_desc->{$cmd}) {
5811 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
5815 my $res = $run_locked->($handler, $parsed);
5818 $reply_err->("failed to handle '$cmd' command - $@")
5821 $reply_err->("unknown command '$cmd' given");
5825 if ($state->{exit}) {
5826 print "mtunnel exited\n";
5828 die "mtunnel exited unexpectedly\n";
5832 my $socket_addr = "/run/qemu-server/$vmid.mtunnel";
5833 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
5834 my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);
5839 socket => $socket_addr,
5843 __PACKAGE__-
>register_method({
5844 name
=> 'mtunnelwebsocket',
5845 path
=> '{vmid}/mtunnelwebsocket',
5848 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.",
5849 user
=> 'all', # check inside
5851 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
5853 additionalProperties
=> 0,
5855 node
=> get_standard_option
('pve-node'),
5856 vmid
=> get_standard_option
('pve-vmid'),
5859 description
=> "unix socket to forward to",
5863 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
5870 port
=> { type
=> 'string', optional
=> 1 },
5871 socket => { type
=> 'string', optional
=> 1 },
5877 my $rpcenv = PVE
::RPCEnvironment
::get
();
5878 my $authuser = $rpcenv->get_user();
5880 my $nodename = PVE
::INotify
::nodename
();
5881 my $node = extract_param
($param, 'node');
5883 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5884 if $node ne 'localhost' && $node ne $nodename;
5886 my $vmid = $param->{vmid
};
5888 PVE
::QemuConfig-
>load_config($vmid);
5890 my $socket = $param->{socket};
5891 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
5893 return { socket => $socket };