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 = {
571 my $check_vm_create_serial_perm = sub {
572 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
574 return 1 if $authuser eq 'root@pam';
576 foreach my $opt (keys %{$param}) {
577 next if $opt !~ m/^serial\d+$/;
579 if ($param->{$opt} eq 'socket') {
580 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
582 die "only root can set '$opt' config for real devices\n";
589 my sub check_usb_perm
{
590 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
592 return 1 if $authuser eq 'root@pam';
594 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
596 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-usb', $value);
597 if ($device->{host
} && $device->{host
} !~ m/^spice$/i) {
598 die "only root can set '$opt' config for real devices\n";
599 } elsif ($device->{mapping
}) {
600 $rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
602 die "either 'host' or 'mapping' must be set.\n";
608 my sub check_vm_create_usb_perm
{
609 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
611 return 1 if $authuser eq 'root@pam';
613 foreach my $opt (keys %{$param}) {
614 next if $opt !~ m/^usb\d+$/;
615 check_usb_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
621 my sub check_hostpci_perm
{
622 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
624 return 1 if $authuser eq 'root@pam';
626 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-hostpci', $value);
627 if ($device->{host
}) {
628 die "only root can set '$opt' config for non-mapped devices\n";
629 } elsif ($device->{mapping
}) {
630 $rpcenv->check_full($authuser, "/mapping/pci/$device->{mapping}", ['Mapping.Use']);
631 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
633 die "either 'host' or 'mapping' must be set.\n";
639 my sub check_vm_create_hostpci_perm
{
640 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
642 return 1 if $authuser eq 'root@pam';
644 foreach my $opt (keys %{$param}) {
645 next if $opt !~ m/^hostpci\d+$/;
646 check_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
652 my $check_vm_modify_config_perm = sub {
653 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
655 return 1 if $authuser eq 'root@pam';
657 foreach my $opt (@$key_list) {
658 # some checks (e.g., disk, serial port, usb) need to be done somewhere
659 # else, as there the permission can be value dependend
660 next if PVE
::QemuServer
::is_valid_drivename
($opt);
661 next if $opt eq 'cdrom';
662 next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
663 next if $opt eq 'tags';
666 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
667 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
668 } elsif ($memoryoptions->{$opt}) {
669 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
670 } elsif ($hwtypeoptions->{$opt}) {
671 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
672 } elsif ($generaloptions->{$opt}) {
673 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
674 # special case for startup since it changes host behaviour
675 if ($opt eq 'startup') {
676 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
678 } elsif ($vmpoweroptions->{$opt}) {
679 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
680 } elsif ($diskoptions->{$opt}) {
681 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
682 } elsif ($opt =~ m/^net\d+$/) {
683 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
684 } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\d+$/) {
685 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
686 } elsif ($opt eq 'vmstate') {
687 # the user needs Disk and PowerMgmt privileges to change the vmstate
688 # also needs privileges on the storage, that will be checked later
689 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
691 # catches args, lock, etc.
692 # new options will be checked here
693 die "only root can set '$opt' config\n";
700 __PACKAGE__-
>register_method({
704 description
=> "Virtual machine index (per node).",
706 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
710 protected
=> 1, # qemu pid files are only readable by root
712 additionalProperties
=> 0,
714 node
=> get_standard_option
('pve-node'),
718 description
=> "Determine the full status of active VMs.",
726 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
728 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
733 my $rpcenv = PVE
::RPCEnvironment
::get
();
734 my $authuser = $rpcenv->get_user();
736 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
739 foreach my $vmid (keys %$vmstatus) {
740 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
742 my $data = $vmstatus->{$vmid};
749 my $parse_restore_archive = sub {
750 my ($storecfg, $archive) = @_;
752 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
756 if (defined($archive_storeid)) {
757 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
758 $res->{volid
} = $archive;
759 if ($scfg->{type
} eq 'pbs') {
760 $res->{type
} = 'pbs';
764 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
765 $res->{type
} = 'file';
766 $res->{path
} = $path;
771 __PACKAGE__-
>register_method({
775 description
=> "Create or restore a virtual machine.",
777 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
778 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
779 "If you create disks you need 'Datastore.AllocateSpace' on any used storage." .
780 "If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.",
781 user
=> 'all', # check inside
786 additionalProperties
=> 0,
787 properties
=> PVE
::QemuServer
::json_config_properties
(
789 node
=> get_standard_option
('pve-node'),
790 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
792 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.",
796 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
798 storage
=> get_standard_option
('pve-storage-id', {
799 description
=> "Default storage.",
801 completion
=> \
&PVE
::QemuServer
::complete_storage
,
806 description
=> "Allow to overwrite existing VM.",
807 requires
=> 'archive',
812 description
=> "Assign a unique random ethernet address.",
813 requires
=> 'archive',
818 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
819 requires
=> 'archive',
823 type
=> 'string', format
=> 'pve-poolid',
824 description
=> "Add the VM to the specified pool.",
827 description
=> "Override I/O bandwidth limit (in KiB/s).",
831 default => 'restore limit from datacenter or storage config',
837 description
=> "Start VM after it was created successfully.",
849 my $rpcenv = PVE
::RPCEnvironment
::get
();
850 my $authuser = $rpcenv->get_user();
852 my $node = extract_param
($param, 'node');
853 my $vmid = extract_param
($param, 'vmid');
855 my $archive = extract_param
($param, 'archive');
856 my $is_restore = !!$archive;
858 my $bwlimit = extract_param
($param, 'bwlimit');
859 my $force = extract_param
($param, 'force');
860 my $pool = extract_param
($param, 'pool');
861 my $start_after_create = extract_param
($param, 'start');
862 my $storage = extract_param
($param, 'storage');
863 my $unique = extract_param
($param, 'unique');
864 my $live_restore = extract_param
($param, 'live-restore');
866 if (defined(my $ssh_keys = $param->{sshkeys
})) {
867 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
868 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
871 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
872 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
874 PVE
::Cluster
::check_cfs_quorum
();
876 my $filename = PVE
::QemuConfig-
>config_file($vmid);
877 my $storecfg = PVE
::Storage
::config
();
879 if (defined($pool)) {
880 $rpcenv->check_pool_exist($pool);
883 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
884 if defined($storage);
886 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
888 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
890 } elsif ($archive && $force && (-f
$filename) &&
891 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
892 # OK: user has VM.Backup permissions and wants to restore an existing VM
898 for my $opt (sort keys $param->%*) {
899 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
900 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
904 if ($archive eq '-') {
905 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
906 $archive = { type
=> 'pipe' };
908 PVE
::Storage
::check_volume_access
(
917 $archive = $parse_restore_archive->($storecfg, $archive);
921 if (scalar(keys $param->%*) > 0) {
922 &$resolve_cdrom_alias($param);
924 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
926 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
928 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
929 check_vm_create_usb_perm
($rpcenv, $authuser, $vmid, $pool, $param);
930 check_vm_create_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $param);
932 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
933 &$check_cpu_model_access($rpcenv, $authuser, $param);
935 $check_drive_param->($param, $storecfg);
937 PVE
::QemuServer
::add_random_macs
($param);
940 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
942 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
943 die "$emsg $@" if $@;
945 my $restored_data = 0;
946 my $restorefn = sub {
947 my $conf = PVE
::QemuConfig-
>load_config($vmid);
949 PVE
::QemuConfig-
>check_protection($conf, $emsg);
951 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
954 my $restore_options = {
959 live
=> $live_restore,
960 override_conf
=> $param,
962 if (my $volid = $archive->{volid
}) {
963 # best effort, real check is after restoring!
965 my $old_conf = PVE
::Storage
::extract_vzdump_config
($storecfg, $volid);
966 PVE
::QemuServer
::restore_merge_config
("backup/qemu-server/$vmid.conf", $old_conf, $param);
969 warn "Could not extract backed up config: $@\n";
970 warn "Skipping early checks!\n";
972 PVE
::QemuServer
::check_restore_permissions
($rpcenv, $authuser, $merged);
975 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
976 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
978 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
979 } elsif ($archive->{type
} eq 'pbs') {
980 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
982 die "unknown backup archive type\n";
986 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
987 # Convert restored VM to template if backup was VM template
988 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
989 warn "Convert to template.\n";
990 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
995 # ensure no old replication state are exists
996 PVE
::ReplicationState
::delete_guest_states
($vmid);
998 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1000 if ($start_after_create && !$live_restore) {
1001 print "Execute autostart\n";
1002 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
1007 my $createfn = sub {
1008 # ensure no old replication state are exists
1009 PVE
::ReplicationState
::delete_guest_states
($vmid);
1013 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1015 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
1019 ($vollist, my $created_opts) = $create_disks->(
1030 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1032 if (!$conf->{boot
}) {
1033 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
1034 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
1037 # auto generate uuid if user did not specify smbios1 option
1038 if (!$conf->{smbios1
}) {
1039 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
1042 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
1043 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
1046 my $machine = $conf->{machine
};
1047 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
1048 # always pin Windows' machine version on create, they get to easily confused
1049 if (PVE
::QemuServer
::Helpers
::windows_version
($conf->{ostype
})) {
1050 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
1054 PVE
::QemuConfig-
>write_config($vmid, $conf);
1060 foreach my $volid (@$vollist) {
1061 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1067 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
1070 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1072 if ($start_after_create) {
1073 print "Execute autostart\n";
1074 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
1079 my ($code, $worker_name);
1081 $worker_name = 'qmrestore';
1083 eval { $restorefn->() };
1085 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
1087 if ($restored_data) {
1088 warn "error after data was restored, VM disks should be OK but config may "
1089 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1091 warn "error before or during data restore, some or all disks were not "
1092 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1098 $worker_name = 'qmcreate';
1100 eval { $createfn->() };
1103 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1104 unlink($conffile) or die "failed to remove config file: $!\n";
1112 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1115 __PACKAGE__-
>register_method({
1120 description
=> "Directory index",
1125 additionalProperties
=> 0,
1127 node
=> get_standard_option
('pve-node'),
1128 vmid
=> get_standard_option
('pve-vmid'),
1136 subdir
=> { type
=> 'string' },
1139 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1145 { subdir
=> 'config' },
1146 { subdir
=> 'cloudinit' },
1147 { subdir
=> 'pending' },
1148 { subdir
=> 'status' },
1149 { subdir
=> 'unlink' },
1150 { subdir
=> 'vncproxy' },
1151 { subdir
=> 'termproxy' },
1152 { subdir
=> 'migrate' },
1153 { subdir
=> 'resize' },
1154 { subdir
=> 'move' },
1155 { subdir
=> 'rrd' },
1156 { subdir
=> 'rrddata' },
1157 { subdir
=> 'monitor' },
1158 { subdir
=> 'agent' },
1159 { subdir
=> 'snapshot' },
1160 { subdir
=> 'spiceproxy' },
1161 { subdir
=> 'sendkey' },
1162 { subdir
=> 'firewall' },
1163 { subdir
=> 'mtunnel' },
1164 { subdir
=> 'remote_migrate' },
1170 __PACKAGE__-
>register_method ({
1171 subclass
=> "PVE::API2::Firewall::VM",
1172 path
=> '{vmid}/firewall',
1175 __PACKAGE__-
>register_method ({
1176 subclass
=> "PVE::API2::Qemu::Agent",
1177 path
=> '{vmid}/agent',
1180 __PACKAGE__-
>register_method({
1182 path
=> '{vmid}/rrd',
1184 protected
=> 1, # fixme: can we avoid that?
1186 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1188 description
=> "Read VM RRD statistics (returns PNG)",
1190 additionalProperties
=> 0,
1192 node
=> get_standard_option
('pve-node'),
1193 vmid
=> get_standard_option
('pve-vmid'),
1195 description
=> "Specify the time frame you are interested in.",
1197 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1200 description
=> "The list of datasources you want to display.",
1201 type
=> 'string', format
=> 'pve-configid-list',
1204 description
=> "The RRD consolidation function",
1206 enum
=> [ 'AVERAGE', 'MAX' ],
1214 filename
=> { type
=> 'string' },
1220 return PVE
::RRD
::create_rrd_graph
(
1221 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1222 $param->{ds
}, $param->{cf
});
1226 __PACKAGE__-
>register_method({
1228 path
=> '{vmid}/rrddata',
1230 protected
=> 1, # fixme: can we avoid that?
1232 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1234 description
=> "Read VM RRD statistics",
1236 additionalProperties
=> 0,
1238 node
=> get_standard_option
('pve-node'),
1239 vmid
=> get_standard_option
('pve-vmid'),
1241 description
=> "Specify the time frame you are interested in.",
1243 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1246 description
=> "The RRD consolidation function",
1248 enum
=> [ 'AVERAGE', 'MAX' ],
1263 return PVE
::RRD
::create_rrd_data
(
1264 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1268 __PACKAGE__-
>register_method({
1269 name
=> 'vm_config',
1270 path
=> '{vmid}/config',
1273 description
=> "Get the virtual machine configuration with pending configuration " .
1274 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1276 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1279 additionalProperties
=> 0,
1281 node
=> get_standard_option
('pve-node'),
1282 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1284 description
=> "Get current values (instead of pending values).",
1289 snapshot
=> get_standard_option
('pve-snapshot-name', {
1290 description
=> "Fetch config values from given snapshot.",
1293 my ($cmd, $pname, $cur, $args) = @_;
1294 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1300 description
=> "The VM configuration.",
1302 properties
=> PVE
::QemuServer
::json_config_properties
({
1305 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1312 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1313 current
=> "cannot use 'snapshot' parameter with 'current'"})
1314 if ($param->{snapshot
} && $param->{current
});
1317 if ($param->{snapshot
}) {
1318 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1320 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1322 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1327 __PACKAGE__-
>register_method({
1328 name
=> 'vm_pending',
1329 path
=> '{vmid}/pending',
1332 description
=> "Get the virtual machine configuration with both current and pending values.",
1334 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1337 additionalProperties
=> 0,
1339 node
=> get_standard_option
('pve-node'),
1340 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1349 description
=> "Configuration option name.",
1353 description
=> "Current value.",
1358 description
=> "Pending value.",
1363 description
=> "Indicates a pending delete request if present and not 0. " .
1364 "The value 2 indicates a force-delete request.",
1376 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1378 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1380 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1381 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1383 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1386 __PACKAGE__-
>register_method({
1387 name
=> 'cloudinit_pending',
1388 path
=> '{vmid}/cloudinit',
1391 description
=> "Get the cloudinit configuration with both current and pending values.",
1393 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1396 additionalProperties
=> 0,
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1408 description
=> "Configuration option name.",
1412 description
=> "Value as it was used to generate the current cloudinit image.",
1417 description
=> "The new pending value.",
1422 description
=> "Indicates a pending delete request if present and not 0. ",
1434 my $vmid = $param->{vmid
};
1435 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1437 my $ci = $conf->{cloudinit
};
1439 $conf->{cipassword
} = '**********' if exists $conf->{cipassword
};
1440 $ci->{cipassword
} = '**********' if exists $ci->{cipassword
};
1444 # All the values that got added
1445 my $added = delete($ci->{added
}) // '';
1446 for my $key (PVE
::Tools
::split_list
($added)) {
1447 push @$res, { key
=> $key, pending
=> $conf->{$key} };
1450 # All already existing values (+ their new value, if it exists)
1451 for my $opt (keys %$cloudinitoptions) {
1452 next if !$conf->{$opt};
1453 next if $added =~ m/$opt/;
1458 if (my $pending = $ci->{$opt}) {
1459 $item->{value
} = $pending;
1460 $item->{pending
} = $conf->{$opt};
1462 $item->{value
} = $conf->{$opt},
1468 # Now, we'll find the deleted ones
1469 for my $opt (keys %$ci) {
1470 next if $conf->{$opt};
1471 push @$res, { key
=> $opt, delete => 1 };
1477 __PACKAGE__-
>register_method({
1478 name
=> 'cloudinit_update',
1479 path
=> '{vmid}/cloudinit',
1483 description
=> "Regenerate and change cloudinit config drive.",
1485 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Cloudinit']],
1488 additionalProperties
=> 0,
1490 node
=> get_standard_option
('pve-node'),
1491 vmid
=> get_standard_option
('pve-vmid'),
1494 returns
=> { type
=> 'null' },
1498 my $rpcenv = PVE
::RPCEnvironment
::get
();
1499 my $authuser = $rpcenv->get_user();
1501 my $vmid = extract_param
($param, 'vmid');
1503 PVE
::QemuConfig-
>lock_config($vmid, sub {
1504 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1505 PVE
::QemuConfig-
>check_lock($conf);
1507 my $storecfg = PVE
::Storage
::config
();
1508 PVE
::QemuServer
::vmconfig_update_cloudinit_drive
($storecfg, $conf, $vmid);
1513 # POST/PUT {vmid}/config implementation
1515 # The original API used PUT (idempotent) an we assumed that all operations
1516 # are fast. But it turned out that almost any configuration change can
1517 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1518 # time to complete and have side effects (not idempotent).
1520 # The new implementation uses POST and forks a worker process. We added
1521 # a new option 'background_delay'. If specified we wait up to
1522 # 'background_delay' second for the worker task to complete. It returns null
1523 # if the task is finished within that time, else we return the UPID.
1525 my $update_vm_api = sub {
1526 my ($param, $sync) = @_;
1528 my $rpcenv = PVE
::RPCEnvironment
::get
();
1530 my $authuser = $rpcenv->get_user();
1532 my $node = extract_param
($param, 'node');
1534 my $vmid = extract_param
($param, 'vmid');
1536 my $digest = extract_param
($param, 'digest');
1538 my $background_delay = extract_param
($param, 'background_delay');
1540 my $skip_cloud_init = extract_param
($param, 'skip_cloud_init');
1542 if (defined(my $cipassword = $param->{cipassword
})) {
1543 # Same logic as in cloud-init (but with the regex fixed...)
1544 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1545 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1548 my @paramarr = (); # used for log message
1549 foreach my $key (sort keys %$param) {
1550 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1551 push @paramarr, "-$key", $value;
1554 my $skiplock = extract_param
($param, 'skiplock');
1555 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1556 if $skiplock && $authuser ne 'root@pam';
1558 my $delete_str = extract_param
($param, 'delete');
1560 my $revert_str = extract_param
($param, 'revert');
1562 my $force = extract_param
($param, 'force');
1564 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1565 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1566 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1569 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
1570 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1572 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1574 my $storecfg = PVE
::Storage
::config
();
1576 my $defaults = PVE
::QemuServer
::load_defaults
();
1578 &$resolve_cdrom_alias($param);
1580 # now try to verify all parameters
1583 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1584 if (!PVE
::QemuServer
::option_exists
($opt)) {
1585 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1588 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1589 "-revert $opt' at the same time" })
1590 if defined($param->{$opt});
1592 $revert->{$opt} = 1;
1596 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1597 $opt = 'ide2' if $opt eq 'cdrom';
1599 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1600 "-delete $opt' at the same time" })
1601 if defined($param->{$opt});
1603 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1604 "-revert $opt' at the same time" })
1607 if (!PVE
::QemuServer
::option_exists
($opt)) {
1608 raise_param_exc
({ delete => "unknown option '$opt'" });
1614 my $repl_conf = PVE
::ReplicationConfig-
>new();
1615 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1616 my $check_replication = sub {
1618 return if !$is_replicated;
1619 my $volid = $drive->{file
};
1620 return if !$volid || !($drive->{replicate
}//1);
1621 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1623 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1624 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1625 if !defined($storeid);
1627 return if defined($volname) && $volname eq 'cloudinit';
1630 if ($volid =~ $NEW_DISK_RE) {
1632 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1634 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1636 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1637 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1638 return if $scfg->{shared
};
1639 die "cannot add non-replicatable volume to a replicated VM\n";
1642 $check_drive_param->($param, $storecfg, $check_replication);
1644 foreach my $opt (keys %$param) {
1645 if ($opt =~ m/^net(\d+)$/) {
1647 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1648 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1649 } elsif ($opt eq 'vmgenid') {
1650 if ($param->{$opt} eq '1') {
1651 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1653 } elsif ($opt eq 'hookscript') {
1654 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1655 raise_param_exc
({ $opt => $@ }) if $@;
1659 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1661 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1663 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1665 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
1667 my $updatefn = sub {
1669 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1671 die "checksum missmatch (file change by other user?)\n"
1672 if $digest && $digest ne $conf->{digest
};
1674 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1676 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1677 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1678 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1679 delete $conf->{lock}; # for check lock check, not written out
1680 push @delete, 'lock'; # this is the real deal to write it out
1682 push @delete, 'runningmachine' if $conf->{runningmachine
};
1683 push @delete, 'runningcpu' if $conf->{runningcpu
};
1686 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1688 foreach my $opt (keys %$revert) {
1689 if (defined($conf->{$opt})) {
1690 $param->{$opt} = $conf->{$opt};
1691 } elsif (defined($conf->{pending
}->{$opt})) {
1696 if ($param->{memory
} || defined($param->{balloon
})) {
1697 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1698 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1700 die "balloon value too large (must be smaller than assigned memory)\n"
1701 if $balloon && $balloon > $maxmem;
1704 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1708 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1710 # write updates to pending section
1712 my $modified = {}; # record what $option we modify
1715 if (my $boot = $conf->{boot
}) {
1716 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1717 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1719 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1721 my $check_drive_perms = sub {
1722 my ($opt, $val) = @_;
1723 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1724 if (PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
1725 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Cloudinit', 'VM.Config.CDROM']);
1726 } elsif (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) { # CDROM
1727 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1729 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1734 foreach my $opt (@delete) {
1735 $modified->{$opt} = 1;
1736 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1738 # value of what we want to delete, independent if pending or not
1739 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1740 if (!defined($val)) {
1741 warn "cannot delete '$opt' - not set in current configuration!\n";
1742 $modified->{$opt} = 0;
1745 my $is_pending_val = defined($conf->{pending
}->{$opt});
1746 delete $conf->{pending
}->{$opt};
1748 # remove from bootorder if necessary
1749 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1750 @bootorder = grep {$_ ne $opt} @bootorder;
1751 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1752 $modified->{boot
} = 1;
1755 if ($opt =~ m/^unused/) {
1756 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1757 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1758 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1759 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1760 delete $conf->{$opt};
1761 PVE
::QemuConfig-
>write_config($vmid, $conf);
1763 } elsif ($opt eq 'vmstate') {
1764 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1765 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1766 delete $conf->{$opt};
1767 PVE
::QemuConfig-
>write_config($vmid, $conf);
1769 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1770 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1771 $check_drive_perms->($opt, $val);
1772 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1774 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1775 PVE
::QemuConfig-
>write_config($vmid, $conf);
1776 } elsif ($opt =~ m/^serial\d+$/) {
1777 if ($val eq 'socket') {
1778 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1779 } elsif ($authuser ne 'root@pam') {
1780 die "only root can delete '$opt' config for real devices\n";
1782 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1783 PVE
::QemuConfig-
>write_config($vmid, $conf);
1784 } elsif ($opt =~ m/^usb\d+$/) {
1785 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1786 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1787 PVE
::QemuConfig-
>write_config($vmid, $conf);
1788 } elsif ($opt =~ m/^hostpci\d+$/) {
1789 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1790 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1791 PVE
::QemuConfig-
>write_config($vmid, $conf);
1792 } elsif ($opt eq 'tags') {
1793 assert_tag_permissions
($vmid, $val, '', $rpcenv, $authuser);
1794 delete $conf->{$opt};
1795 PVE
::QemuConfig-
>write_config($vmid, $conf);
1796 } elsif ($opt =~ m/^net\d+$/) {
1797 if ($conf->{$opt}) {
1798 PVE
::QemuServer
::check_bridge_access
(
1801 { $opt => $conf->{$opt} },
1804 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1805 PVE
::QemuConfig-
>write_config($vmid, $conf);
1807 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1808 PVE
::QemuConfig-
>write_config($vmid, $conf);
1812 foreach my $opt (keys %$param) { # add/change
1813 $modified->{$opt} = 1;
1814 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1815 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1817 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1819 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1821 if ($conf->{$opt}) {
1822 $check_drive_perms->($opt, $conf->{$opt});
1826 $check_drive_perms->($opt, $param->{$opt});
1827 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1828 if defined($conf->{pending
}->{$opt});
1830 my (undef, $created_opts) = $create_disks->(
1838 {$opt => $param->{$opt}},
1840 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1842 # default legacy boot order implies all cdroms anyway
1844 # append new CD drives to bootorder to mark them bootable
1845 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1846 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1847 push @bootorder, $opt;
1848 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1849 $modified->{boot
} = 1;
1852 } elsif ($opt =~ m/^serial\d+/) {
1853 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1854 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1855 } elsif ($authuser ne 'root@pam') {
1856 die "only root can modify '$opt' config for real devices\n";
1858 $conf->{pending
}->{$opt} = $param->{$opt};
1859 } elsif ($opt =~ m/^usb\d+/) {
1860 if (my $olddevice = $conf->{$opt}) {
1861 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});
1863 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1864 $conf->{pending
}->{$opt} = $param->{$opt};
1865 } elsif ($opt =~ m/^hostpci\d+$/) {
1866 if (my $oldvalue = $conf->{$opt}) {
1867 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);
1869 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1870 $conf->{pending
}->{$opt} = $param->{$opt};
1871 } elsif ($opt eq 'tags') {
1872 assert_tag_permissions
($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
1873 $conf->{pending
}->{$opt} = PVE
::GuestHelpers
::get_unique_tags
($param->{$opt});
1874 } elsif ($opt =~ m/^net\d+$/) {
1875 if ($conf->{$opt}) {
1876 PVE
::QemuServer
::check_bridge_access
(
1879 { $opt => $conf->{$opt} },
1882 $conf->{pending
}->{$opt} = $param->{$opt};
1884 $conf->{pending
}->{$opt} = $param->{$opt};
1886 if ($opt eq 'boot') {
1887 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1888 if ($new_bootcfg->{order
}) {
1889 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1890 for my $dev (@devs) {
1891 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1892 my $deleted = grep {$_ eq $dev} @delete;
1893 die "invalid bootorder: device '$dev' does not exist'\n"
1894 if !$exists || $deleted;
1897 # remove legacy boot order settings if new one set
1898 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1899 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1900 if $conf->{bootdisk
};
1904 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1905 PVE
::QemuConfig-
>write_config($vmid, $conf);
1908 # remove pending changes when nothing changed
1909 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1910 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1911 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1913 return if !scalar(keys %{$conf->{pending
}});
1915 my $running = PVE
::QemuServer
::check_running
($vmid);
1917 # apply pending changes
1919 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1923 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1925 # cloud_init must be skipped if we are in an incoming, remote live migration
1926 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors, $skip_cloud_init);
1928 raise_param_exc
($errors) if scalar(keys %$errors);
1937 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1939 if ($background_delay) {
1941 # Note: It would be better to do that in the Event based HTTPServer
1942 # to avoid blocking call to sleep.
1944 my $end_time = time() + $background_delay;
1946 my $task = PVE
::Tools
::upid_decode
($upid);
1949 while (time() < $end_time) {
1950 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1952 sleep(1); # this gets interrupted when child process ends
1956 my $status = PVE
::Tools
::upid_read_status
($upid);
1957 return if !PVE
::Tools
::upid_status_is_error
($status);
1958 die "failed to update VM $vmid: $status\n";
1966 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1969 my $vm_config_perm_list = [
1974 'VM.Config.Network',
1976 'VM.Config.Options',
1977 'VM.Config.Cloudinit',
1980 __PACKAGE__-
>register_method({
1981 name
=> 'update_vm_async',
1982 path
=> '{vmid}/config',
1986 description
=> "Set virtual machine options (asynchrounous API).",
1988 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1991 additionalProperties
=> 0,
1992 properties
=> PVE
::QemuServer
::json_config_properties
(
1994 node
=> get_standard_option
('pve-node'),
1995 vmid
=> get_standard_option
('pve-vmid'),
1996 skiplock
=> get_standard_option
('skiplock'),
1998 type
=> 'string', format
=> 'pve-configid-list',
1999 description
=> "A list of settings you want to delete.",
2003 type
=> 'string', format
=> 'pve-configid-list',
2004 description
=> "Revert a pending change.",
2009 description
=> $opt_force_description,
2011 requires
=> 'delete',
2015 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2019 background_delay
=> {
2021 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
2027 1, # with_disk_alloc
2034 code
=> $update_vm_api,
2037 __PACKAGE__-
>register_method({
2038 name
=> 'update_vm',
2039 path
=> '{vmid}/config',
2043 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
2045 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
2048 additionalProperties
=> 0,
2049 properties
=> PVE
::QemuServer
::json_config_properties
(
2051 node
=> get_standard_option
('pve-node'),
2052 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2053 skiplock
=> get_standard_option
('skiplock'),
2055 type
=> 'string', format
=> 'pve-configid-list',
2056 description
=> "A list of settings you want to delete.",
2060 type
=> 'string', format
=> 'pve-configid-list',
2061 description
=> "Revert a pending change.",
2066 description
=> $opt_force_description,
2068 requires
=> 'delete',
2072 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2077 1, # with_disk_alloc
2080 returns
=> { type
=> 'null' },
2083 &$update_vm_api($param, 1);
2088 __PACKAGE__-
>register_method({
2089 name
=> 'destroy_vm',
2094 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
2095 ." and firewall rules",
2097 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2100 additionalProperties
=> 0,
2102 node
=> get_standard_option
('pve-node'),
2103 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2104 skiplock
=> get_standard_option
('skiplock'),
2107 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
2110 'destroy-unreferenced-disks' => {
2112 description
=> "If set, destroy additionally all disks not referenced in the config"
2113 ." but with a matching VMID from all enabled storages.",
2125 my $rpcenv = PVE
::RPCEnvironment
::get
();
2126 my $authuser = $rpcenv->get_user();
2127 my $vmid = $param->{vmid
};
2129 my $skiplock = $param->{skiplock
};
2130 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2131 if $skiplock && $authuser ne 'root@pam';
2133 my $early_checks = sub {
2135 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2136 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
2138 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
2140 if (!$param->{purge
}) {
2141 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
2143 # don't allow destroy if with replication jobs but no purge param
2144 my $repl_conf = PVE
::ReplicationConfig-
>new();
2145 $repl_conf->check_for_existing_jobs($vmid);
2148 die "VM $vmid is running - destroy failed\n"
2149 if PVE
::QemuServer
::check_running
($vmid);
2159 my $storecfg = PVE
::Storage
::config
();
2161 syslog
('info', "destroy VM $vmid: $upid\n");
2162 PVE
::QemuConfig-
>lock_config($vmid, sub {
2163 # repeat, config might have changed
2164 my $ha_managed = $early_checks->();
2166 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
2168 PVE
::QemuServer
::destroy_vm
(
2171 $skiplock, { lock => 'destroyed' },
2172 $purge_unreferenced,
2175 PVE
::AccessControl
::remove_vm_access
($vmid);
2176 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2177 if ($param->{purge
}) {
2178 print "purging VM $vmid from related configurations..\n";
2179 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2180 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2183 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2184 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2188 # only now remove the zombie config, else we can have reuse race
2189 PVE
::QemuConfig-
>destroy_config($vmid);
2193 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2196 __PACKAGE__-
>register_method({
2198 path
=> '{vmid}/unlink',
2202 description
=> "Unlink/delete disk images.",
2204 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2207 additionalProperties
=> 0,
2209 node
=> get_standard_option
('pve-node'),
2210 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2212 type
=> 'string', format
=> 'pve-configid-list',
2213 description
=> "A list of disk IDs you want to delete.",
2217 description
=> $opt_force_description,
2222 returns
=> { type
=> 'null'},
2226 $param->{delete} = extract_param
($param, 'idlist');
2228 __PACKAGE__-
>update_vm($param);
2233 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2234 my $gen_rand_chars = sub {
2237 die "invalid length $length" if $length < 1;
2239 my $min = ord('!'); # first printable ascii
2241 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2242 die "failed to generate random bytes!\n"
2245 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2252 __PACKAGE__-
>register_method({
2254 path
=> '{vmid}/vncproxy',
2258 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2260 description
=> "Creates a TCP VNC proxy connections.",
2262 additionalProperties
=> 0,
2264 node
=> get_standard_option
('pve-node'),
2265 vmid
=> get_standard_option
('pve-vmid'),
2269 description
=> "starts websockify instead of vncproxy",
2271 'generate-password' => {
2275 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2280 additionalProperties
=> 0,
2282 user
=> { type
=> 'string' },
2283 ticket
=> { type
=> 'string' },
2286 description
=> "Returned if requested with 'generate-password' param."
2287 ." Consists of printable ASCII characters ('!' .. '~').",
2290 cert
=> { type
=> 'string' },
2291 port
=> { type
=> 'integer' },
2292 upid
=> { type
=> 'string' },
2298 my $rpcenv = PVE
::RPCEnvironment
::get
();
2300 my $authuser = $rpcenv->get_user();
2302 my $vmid = $param->{vmid
};
2303 my $node = $param->{node
};
2304 my $websocket = $param->{websocket
};
2306 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2310 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2311 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2314 my $authpath = "/vms/$vmid";
2316 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2317 my $password = $ticket;
2318 if ($param->{'generate-password'}) {
2319 $password = $gen_rand_chars->(8);
2322 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2328 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2329 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2330 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2331 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2332 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2334 $family = PVE
::Tools
::get_host_address_family
($node);
2337 my $port = PVE
::Tools
::next_vnc_port
($family);
2344 syslog
('info', "starting vnc proxy $upid\n");
2348 if (defined($serial)) {
2350 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2352 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2353 '-timeout', $timeout, '-authpath', $authpath,
2354 '-perm', 'Sys.Console'];
2356 if ($param->{websocket
}) {
2357 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2358 push @$cmd, '-notls', '-listen', 'localhost';
2361 push @$cmd, '-c', @$remcmd, @$termcmd;
2363 PVE
::Tools
::run_command
($cmd);
2367 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
2369 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2371 my $sock = IO
::Socket
::IP-
>new(
2376 GetAddrInfoFlags
=> 0,
2377 ) or die "failed to create socket: $!\n";
2378 # Inside the worker we shouldn't have any previous alarms
2379 # running anyway...:
2381 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2383 accept(my $cli, $sock) or die "connection failed: $!\n";
2386 if (PVE
::Tools
::run_command
($cmd,
2387 output
=> '>&'.fileno($cli),
2388 input
=> '<&'.fileno($cli),
2391 die "Failed to run vncproxy.\n";
2398 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2400 PVE
::Tools
::wait_for_vnc_port
($port);
2409 $res->{password
} = $password if $param->{'generate-password'};
2414 __PACKAGE__-
>register_method({
2415 name
=> 'termproxy',
2416 path
=> '{vmid}/termproxy',
2420 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2422 description
=> "Creates a TCP proxy connections.",
2424 additionalProperties
=> 0,
2426 node
=> get_standard_option
('pve-node'),
2427 vmid
=> get_standard_option
('pve-vmid'),
2431 enum
=> [qw(serial0 serial1 serial2 serial3)],
2432 description
=> "opens a serial terminal (defaults to display)",
2437 additionalProperties
=> 0,
2439 user
=> { type
=> 'string' },
2440 ticket
=> { type
=> 'string' },
2441 port
=> { type
=> 'integer' },
2442 upid
=> { type
=> 'string' },
2448 my $rpcenv = PVE
::RPCEnvironment
::get
();
2450 my $authuser = $rpcenv->get_user();
2452 my $vmid = $param->{vmid
};
2453 my $node = $param->{node
};
2454 my $serial = $param->{serial
};
2456 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2458 if (!defined($serial)) {
2460 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2461 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2465 my $authpath = "/vms/$vmid";
2467 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2472 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2473 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2474 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2475 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2476 push @$remcmd, '--';
2478 $family = PVE
::Tools
::get_host_address_family
($node);
2481 my $port = PVE
::Tools
::next_vnc_port
($family);
2483 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2484 push @$termcmd, '-iface', $serial if $serial;
2489 syslog
('info', "starting qemu termproxy $upid\n");
2491 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2492 '--perm', 'VM.Console', '--'];
2493 push @$cmd, @$remcmd, @$termcmd;
2495 PVE
::Tools
::run_command
($cmd);
2498 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2500 PVE
::Tools
::wait_for_vnc_port
($port);
2510 __PACKAGE__-
>register_method({
2511 name
=> 'vncwebsocket',
2512 path
=> '{vmid}/vncwebsocket',
2515 description
=> "You also need to pass a valid ticket (vncticket).",
2516 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2518 description
=> "Opens a weksocket for VNC traffic.",
2520 additionalProperties
=> 0,
2522 node
=> get_standard_option
('pve-node'),
2523 vmid
=> get_standard_option
('pve-vmid'),
2525 description
=> "Ticket from previous call to vncproxy.",
2530 description
=> "Port number returned by previous vncproxy call.",
2540 port
=> { type
=> 'string' },
2546 my $rpcenv = PVE
::RPCEnvironment
::get
();
2548 my $authuser = $rpcenv->get_user();
2550 my $vmid = $param->{vmid
};
2551 my $node = $param->{node
};
2553 my $authpath = "/vms/$vmid";
2555 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2557 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2559 # Note: VNC ports are acessible from outside, so we do not gain any
2560 # security if we verify that $param->{port} belongs to VM $vmid. This
2561 # check is done by verifying the VNC ticket (inside VNC protocol).
2563 my $port = $param->{port
};
2565 return { port
=> $port };
2568 __PACKAGE__-
>register_method({
2569 name
=> 'spiceproxy',
2570 path
=> '{vmid}/spiceproxy',
2575 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2577 description
=> "Returns a SPICE configuration to connect to the VM.",
2579 additionalProperties
=> 0,
2581 node
=> get_standard_option
('pve-node'),
2582 vmid
=> get_standard_option
('pve-vmid'),
2583 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2586 returns
=> get_standard_option
('remote-viewer-config'),
2590 my $rpcenv = PVE
::RPCEnvironment
::get
();
2592 my $authuser = $rpcenv->get_user();
2594 my $vmid = $param->{vmid
};
2595 my $node = $param->{node
};
2596 my $proxy = $param->{proxy
};
2598 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2599 my $title = "VM $vmid";
2600 $title .= " - ". $conf->{name
} if $conf->{name
};
2602 my $port = PVE
::QemuServer
::spice_port
($vmid);
2604 my ($ticket, undef, $remote_viewer_config) =
2605 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2607 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2608 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2610 return $remote_viewer_config;
2613 __PACKAGE__-
>register_method({
2615 path
=> '{vmid}/status',
2618 description
=> "Directory index",
2623 additionalProperties
=> 0,
2625 node
=> get_standard_option
('pve-node'),
2626 vmid
=> get_standard_option
('pve-vmid'),
2634 subdir
=> { type
=> 'string' },
2637 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2643 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2646 { subdir
=> 'current' },
2647 { subdir
=> 'start' },
2648 { subdir
=> 'stop' },
2649 { subdir
=> 'reset' },
2650 { subdir
=> 'shutdown' },
2651 { subdir
=> 'suspend' },
2652 { subdir
=> 'reboot' },
2658 __PACKAGE__-
>register_method({
2659 name
=> 'vm_status',
2660 path
=> '{vmid}/status/current',
2663 protected
=> 1, # qemu pid files are only readable by root
2664 description
=> "Get virtual machine status.",
2666 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2669 additionalProperties
=> 0,
2671 node
=> get_standard_option
('pve-node'),
2672 vmid
=> get_standard_option
('pve-vmid'),
2678 %$PVE::QemuServer
::vmstatus_return_properties
,
2680 description
=> "HA manager service status.",
2684 description
=> "QEMU VGA configuration supports spice.",
2689 description
=> "QEMU Guest Agent is enabled in config.",
2699 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2701 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2702 my $status = $vmstatus->{$param->{vmid
}};
2704 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2707 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2708 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2709 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2710 $status->{spice
} = 1 if $spice;
2712 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2717 __PACKAGE__-
>register_method({
2719 path
=> '{vmid}/status/start',
2723 description
=> "Start virtual machine.",
2725 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2728 additionalProperties
=> 0,
2730 node
=> get_standard_option
('pve-node'),
2731 vmid
=> get_standard_option
('pve-vmid',
2732 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2733 skiplock
=> get_standard_option
('skiplock'),
2734 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2735 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2738 enum
=> ['secure', 'insecure'],
2739 description
=> "Migration traffic is encrypted using an SSH " .
2740 "tunnel by default. On secure, completely private networks " .
2741 "this can be disabled to increase performance.",
2744 migration_network
=> {
2745 type
=> 'string', format
=> 'CIDR',
2746 description
=> "CIDR of the (sub) network that is used for migration.",
2749 machine
=> get_standard_option
('pve-qemu-machine'),
2751 description
=> "Override QEMU's -cpu argument with the given string.",
2755 targetstorage
=> get_standard_option
('pve-targetstorage'),
2757 description
=> "Wait maximal timeout seconds.",
2760 default => 'max(30, vm memory in GiB)',
2771 my $rpcenv = PVE
::RPCEnvironment
::get
();
2772 my $authuser = $rpcenv->get_user();
2774 my $node = extract_param
($param, 'node');
2775 my $vmid = extract_param
($param, 'vmid');
2776 my $timeout = extract_param
($param, 'timeout');
2777 my $machine = extract_param
($param, 'machine');
2779 my $get_root_param = sub {
2780 my $value = extract_param
($param, $_[0]);
2781 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2782 if $value && $authuser ne 'root@pam';
2786 my $stateuri = $get_root_param->('stateuri');
2787 my $skiplock = $get_root_param->('skiplock');
2788 my $migratedfrom = $get_root_param->('migratedfrom');
2789 my $migration_type = $get_root_param->('migration_type');
2790 my $migration_network = $get_root_param->('migration_network');
2791 my $targetstorage = $get_root_param->('targetstorage');
2792 my $force_cpu = $get_root_param->('force-cpu');
2796 if ($targetstorage) {
2797 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2799 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2800 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2804 # read spice ticket from STDIN
2806 my $nbd_protocol_version = 0;
2807 my $replicated_volumes = {};
2808 my $offline_volumes = {};
2809 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2810 while (defined(my $line = <STDIN
>)) {
2812 if ($line =~ m/^spice_ticket: (.+)$/) {
2814 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2815 $nbd_protocol_version = $1;
2816 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2817 $replicated_volumes->{$1} = 1;
2818 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2819 $offline_volumes->{tpmstate0
} = $1;
2820 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2821 $offline_volumes->{$1} = $2;
2822 } elsif (!$spice_ticket) {
2823 # fallback for old source node
2824 $spice_ticket = $line;
2826 warn "unknown 'start' parameter on STDIN: '$line'\n";
2831 PVE
::Cluster
::check_cfs_quorum
();
2833 my $storecfg = PVE
::Storage
::config
();
2835 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2839 print "Requesting HA start for VM $vmid\n";
2841 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2842 PVE
::Tools
::run_command
($cmd);
2846 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2853 syslog
('info', "start VM $vmid: $upid\n");
2855 my $migrate_opts = {
2856 migratedfrom
=> $migratedfrom,
2857 spice_ticket
=> $spice_ticket,
2858 network
=> $migration_network,
2859 type
=> $migration_type,
2860 storagemap
=> $storagemap,
2861 nbd_proto_version
=> $nbd_protocol_version,
2862 replicated_volumes
=> $replicated_volumes,
2863 offline_volumes
=> $offline_volumes,
2867 statefile
=> $stateuri,
2868 skiplock
=> $skiplock,
2869 forcemachine
=> $machine,
2870 timeout
=> $timeout,
2871 forcecpu
=> $force_cpu,
2874 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2878 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2882 __PACKAGE__-
>register_method({
2884 path
=> '{vmid}/status/stop',
2888 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2889 "is akin to pulling the power plug of a running computer and may damage the VM data",
2891 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2894 additionalProperties
=> 0,
2896 node
=> get_standard_option
('pve-node'),
2897 vmid
=> get_standard_option
('pve-vmid',
2898 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2899 skiplock
=> get_standard_option
('skiplock'),
2900 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2902 description
=> "Wait maximal timeout seconds.",
2908 description
=> "Do not deactivate storage volumes.",
2921 my $rpcenv = PVE
::RPCEnvironment
::get
();
2922 my $authuser = $rpcenv->get_user();
2924 my $node = extract_param
($param, 'node');
2925 my $vmid = extract_param
($param, 'vmid');
2927 my $skiplock = extract_param
($param, 'skiplock');
2928 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2929 if $skiplock && $authuser ne 'root@pam';
2931 my $keepActive = extract_param
($param, 'keepActive');
2932 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2933 if $keepActive && $authuser ne 'root@pam';
2935 my $migratedfrom = extract_param
($param, 'migratedfrom');
2936 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2937 if $migratedfrom && $authuser ne 'root@pam';
2940 my $storecfg = PVE
::Storage
::config
();
2942 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2947 print "Requesting HA stop for VM $vmid\n";
2949 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2950 PVE
::Tools
::run_command
($cmd);
2954 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2960 syslog
('info', "stop VM $vmid: $upid\n");
2962 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2963 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2967 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2971 __PACKAGE__-
>register_method({
2973 path
=> '{vmid}/status/reset',
2977 description
=> "Reset virtual machine.",
2979 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2982 additionalProperties
=> 0,
2984 node
=> get_standard_option
('pve-node'),
2985 vmid
=> get_standard_option
('pve-vmid',
2986 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2987 skiplock
=> get_standard_option
('skiplock'),
2996 my $rpcenv = PVE
::RPCEnvironment
::get
();
2998 my $authuser = $rpcenv->get_user();
3000 my $node = extract_param
($param, 'node');
3002 my $vmid = extract_param
($param, 'vmid');
3004 my $skiplock = extract_param
($param, 'skiplock');
3005 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3006 if $skiplock && $authuser ne 'root@pam';
3008 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3013 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
3018 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
3021 __PACKAGE__-
>register_method({
3022 name
=> 'vm_shutdown',
3023 path
=> '{vmid}/status/shutdown',
3027 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
3028 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
3030 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3033 additionalProperties
=> 0,
3035 node
=> get_standard_option
('pve-node'),
3036 vmid
=> get_standard_option
('pve-vmid',
3037 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3038 skiplock
=> get_standard_option
('skiplock'),
3040 description
=> "Wait maximal timeout seconds.",
3046 description
=> "Make sure the VM stops.",
3052 description
=> "Do not deactivate storage volumes.",
3065 my $rpcenv = PVE
::RPCEnvironment
::get
();
3066 my $authuser = $rpcenv->get_user();
3068 my $node = extract_param
($param, 'node');
3069 my $vmid = extract_param
($param, 'vmid');
3071 my $skiplock = extract_param
($param, 'skiplock');
3072 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3073 if $skiplock && $authuser ne 'root@pam';
3075 my $keepActive = extract_param
($param, 'keepActive');
3076 raise_param_exc
({ keepActive
=> "Only root may use this option." })
3077 if $keepActive && $authuser ne 'root@pam';
3079 my $storecfg = PVE
::Storage
::config
();
3083 # if vm is paused, do not shutdown (but stop if forceStop = 1)
3084 # otherwise, we will infer a shutdown command, but run into the timeout,
3085 # then when the vm is resumed, it will instantly shutdown
3087 # checking the qmp status here to get feedback to the gui/cli/api
3088 # and the status query should not take too long
3089 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
3090 if ($param->{forceStop
}) {
3091 warn "VM is paused - stop instead of shutdown\n";
3094 die "VM is paused - cannot shutdown\n";
3098 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3100 my $timeout = $param->{timeout
} // 60;
3104 print "Requesting HA stop for VM $vmid\n";
3106 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
3107 PVE
::Tools
::run_command
($cmd);
3111 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3118 syslog
('info', "shutdown VM $vmid: $upid\n");
3120 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
3121 $shutdown, $param->{forceStop
}, $keepActive);
3125 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
3129 __PACKAGE__-
>register_method({
3130 name
=> 'vm_reboot',
3131 path
=> '{vmid}/status/reboot',
3135 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
3137 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3140 additionalProperties
=> 0,
3142 node
=> get_standard_option
('pve-node'),
3143 vmid
=> get_standard_option
('pve-vmid',
3144 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3146 description
=> "Wait maximal timeout seconds for the shutdown.",
3159 my $rpcenv = PVE
::RPCEnvironment
::get
();
3160 my $authuser = $rpcenv->get_user();
3162 my $node = extract_param
($param, 'node');
3163 my $vmid = extract_param
($param, 'vmid');
3165 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
3167 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3172 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3173 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3177 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3180 __PACKAGE__-
>register_method({
3181 name
=> 'vm_suspend',
3182 path
=> '{vmid}/status/suspend',
3186 description
=> "Suspend virtual machine.",
3188 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3189 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3190 " on the storage for the vmstate.",
3191 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3194 additionalProperties
=> 0,
3196 node
=> get_standard_option
('pve-node'),
3197 vmid
=> get_standard_option
('pve-vmid',
3198 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3199 skiplock
=> get_standard_option
('skiplock'),
3204 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3206 statestorage
=> get_standard_option
('pve-storage-id', {
3207 description
=> "The storage for the VM state",
3208 requires
=> 'todisk',
3210 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3220 my $rpcenv = PVE
::RPCEnvironment
::get
();
3221 my $authuser = $rpcenv->get_user();
3223 my $node = extract_param
($param, 'node');
3224 my $vmid = extract_param
($param, 'vmid');
3226 my $todisk = extract_param
($param, 'todisk') // 0;
3228 my $statestorage = extract_param
($param, 'statestorage');
3230 my $skiplock = extract_param
($param, 'skiplock');
3231 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3232 if $skiplock && $authuser ne 'root@pam';
3234 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3236 die "Cannot suspend HA managed VM to disk\n"
3237 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3239 # early check for storage permission, for better user feedback
3241 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3242 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3244 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3245 for my $key (keys %$conf) {
3246 next if $key !~ /^hostpci\d+/;
3247 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3248 ." possibility to save/restore their internal state\n";
3251 if (!$statestorage) {
3252 # get statestorage from config if none is given
3253 my $storecfg = PVE
::Storage
::config
();
3254 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3257 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3263 syslog
('info', "suspend VM $vmid: $upid\n");
3265 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3270 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3271 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3274 __PACKAGE__-
>register_method({
3275 name
=> 'vm_resume',
3276 path
=> '{vmid}/status/resume',
3280 description
=> "Resume virtual machine.",
3282 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3285 additionalProperties
=> 0,
3287 node
=> get_standard_option
('pve-node'),
3288 vmid
=> get_standard_option
('pve-vmid',
3289 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3290 skiplock
=> get_standard_option
('skiplock'),
3291 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3301 my $rpcenv = PVE
::RPCEnvironment
::get
();
3303 my $authuser = $rpcenv->get_user();
3305 my $node = extract_param
($param, 'node');
3307 my $vmid = extract_param
($param, 'vmid');
3309 my $skiplock = extract_param
($param, 'skiplock');
3310 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3311 if $skiplock && $authuser ne 'root@pam';
3313 # nocheck is used as part of migration when config file might be still
3315 my $nocheck = extract_param
($param, 'nocheck');
3316 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3317 if $nocheck && $authuser ne 'root@pam';
3319 my $to_disk_suspended;
3321 PVE
::QemuConfig-
>lock_config($vmid, sub {
3322 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3323 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3327 die "VM $vmid not running\n"
3328 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3333 syslog
('info', "resume VM $vmid: $upid\n");
3335 if (!$to_disk_suspended) {
3336 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3338 my $storecfg = PVE
::Storage
::config
();
3339 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3345 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3348 __PACKAGE__-
>register_method({
3349 name
=> 'vm_sendkey',
3350 path
=> '{vmid}/sendkey',
3354 description
=> "Send key event to virtual machine.",
3356 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3359 additionalProperties
=> 0,
3361 node
=> get_standard_option
('pve-node'),
3362 vmid
=> get_standard_option
('pve-vmid',
3363 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3364 skiplock
=> get_standard_option
('skiplock'),
3366 description
=> "The key (qemu monitor encoding).",
3371 returns
=> { type
=> 'null'},
3375 my $rpcenv = PVE
::RPCEnvironment
::get
();
3377 my $authuser = $rpcenv->get_user();
3379 my $node = extract_param
($param, 'node');
3381 my $vmid = extract_param
($param, 'vmid');
3383 my $skiplock = extract_param
($param, 'skiplock');
3384 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3385 if $skiplock && $authuser ne 'root@pam';
3387 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3392 __PACKAGE__-
>register_method({
3393 name
=> 'vm_feature',
3394 path
=> '{vmid}/feature',
3398 description
=> "Check if feature for virtual machine is available.",
3400 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3403 additionalProperties
=> 0,
3405 node
=> get_standard_option
('pve-node'),
3406 vmid
=> get_standard_option
('pve-vmid'),
3408 description
=> "Feature to check.",
3410 enum
=> [ 'snapshot', 'clone', 'copy' ],
3412 snapname
=> get_standard_option
('pve-snapshot-name', {
3420 hasFeature
=> { type
=> 'boolean' },
3423 items
=> { type
=> 'string' },
3430 my $node = extract_param
($param, 'node');
3432 my $vmid = extract_param
($param, 'vmid');
3434 my $snapname = extract_param
($param, 'snapname');
3436 my $feature = extract_param
($param, 'feature');
3438 my $running = PVE
::QemuServer
::check_running
($vmid);
3440 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3443 my $snap = $conf->{snapshots
}->{$snapname};
3444 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3447 my $storecfg = PVE
::Storage
::config
();
3449 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3450 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3453 hasFeature
=> $hasFeature,
3454 nodes
=> [ keys %$nodelist ],
3458 __PACKAGE__-
>register_method({
3460 path
=> '{vmid}/clone',
3464 description
=> "Create a copy of virtual machine/template.",
3466 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3467 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3468 "'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet",
3471 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3473 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3474 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3479 additionalProperties
=> 0,
3481 node
=> get_standard_option
('pve-node'),
3482 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3483 newid
=> get_standard_option
('pve-vmid', {
3484 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3485 description
=> 'VMID for the clone.' }),
3488 type
=> 'string', format
=> 'dns-name',
3489 description
=> "Set a name for the new VM.",
3494 description
=> "Description for the new VM.",
3498 type
=> 'string', format
=> 'pve-poolid',
3499 description
=> "Add the new VM to the specified pool.",
3501 snapname
=> get_standard_option
('pve-snapshot-name', {
3504 storage
=> get_standard_option
('pve-storage-id', {
3505 description
=> "Target storage for full clone.",
3509 description
=> "Target format for file storage. Only valid for full clone.",
3512 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3517 description
=> "Create a full copy of all disks. This is always done when " .
3518 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3520 target
=> get_standard_option
('pve-node', {
3521 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3525 description
=> "Override I/O bandwidth limit (in KiB/s).",
3529 default => 'clone limit from datacenter or storage config',
3539 my $rpcenv = PVE
::RPCEnvironment
::get
();
3540 my $authuser = $rpcenv->get_user();
3542 my $node = extract_param
($param, 'node');
3543 my $vmid = extract_param
($param, 'vmid');
3544 my $newid = extract_param
($param, 'newid');
3545 my $pool = extract_param
($param, 'pool');
3547 my $snapname = extract_param
($param, 'snapname');
3548 my $storage = extract_param
($param, 'storage');
3549 my $format = extract_param
($param, 'format');
3550 my $target = extract_param
($param, 'target');
3552 my $localnode = PVE
::INotify
::nodename
();
3554 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3558 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3560 my $load_and_check = sub {
3561 $rpcenv->check_pool_exist($pool) if defined($pool);
3562 PVE
::Cluster
::check_node_exists
($target) if $target;
3564 my $storecfg = PVE
::Storage
::config
();
3567 # check if storage is enabled on local node
3568 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3570 # check if storage is available on target node
3571 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3572 # clone only works if target storage is shared
3573 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3574 die "can't clone to non-shared storage '$storage'\n"
3575 if !$scfg->{shared
};
3579 PVE
::Cluster
::check_cfs_quorum
();
3581 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3582 PVE
::QemuConfig-
>check_lock($conf);
3584 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3585 die "unexpected state change\n" if $verify_running != $running;
3587 die "snapshot '$snapname' does not exist\n"
3588 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3590 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3592 die "parameter 'storage' not allowed for linked clones\n"
3593 if defined($storage) && !$full;
3595 die "parameter 'format' not allowed for linked clones\n"
3596 if defined($format) && !$full;
3598 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3600 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3601 PVE
::QemuServer
::check_mapping_access
($rpcenv, $authuser, $oldconf);
3603 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $oldconf);
3605 die "can't clone VM to node '$target' (VM uses local storage)\n"
3606 if $target && !$sharedvm;
3608 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3609 die "unable to create VM $newid: config file already exists\n"
3612 my $newconf = { lock => 'clone' };
3617 foreach my $opt (keys %$oldconf) {
3618 my $value = $oldconf->{$opt};
3620 # do not copy snapshot related info
3621 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3622 $opt eq 'vmstate' || $opt eq 'snapstate';
3624 # no need to copy unused images, because VMID(owner) changes anyways
3625 next if $opt =~ m/^unused\d+$/;
3627 die "cannot clone TPM state while VM is running\n"
3628 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3630 # always change MAC! address
3631 if ($opt =~ m/^net(\d+)$/) {
3632 my $net = PVE
::QemuServer
::parse_net
($value);
3633 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3634 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3635 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3636 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3637 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3638 die "unable to parse drive options for '$opt'\n" if !$drive;
3639 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3640 $newconf->{$opt} = $value; # simply copy configuration
3642 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3643 die "Full clone feature is not supported for drive '$opt'\n"
3644 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3645 $fullclone->{$opt} = 1;
3647 # not full means clone instead of copy
3648 die "Linked clone feature is not supported for drive '$opt'\n"
3649 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3651 $drives->{$opt} = $drive;
3652 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3653 push @$vollist, $drive->{file
};
3656 # copy everything else
3657 $newconf->{$opt} = $value;
3661 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3665 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3666 my $storecfg = PVE
::Storage
::config
();
3668 # auto generate a new uuid
3669 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3670 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3671 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3672 # auto generate a new vmgenid only if the option was set for template
3673 if ($newconf->{vmgenid
}) {
3674 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3677 delete $newconf->{template
};
3679 if ($param->{name
}) {
3680 $newconf->{name
} = $param->{name
};
3682 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3685 if ($param->{description
}) {
3686 $newconf->{description
} = $param->{description
};
3689 # create empty/temp config - this fails if VM already exists on other node
3690 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3691 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3693 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3695 my $newvollist = [];
3702 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3704 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3706 my $bwlimit = extract_param
($param, 'bwlimit');
3708 my $total_jobs = scalar(keys %{$drives});
3711 foreach my $opt (sort keys %$drives) {
3712 my $drive = $drives->{$opt};
3713 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3714 my $completion = $skipcomplete ?
'skip' : 'complete';
3716 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3717 my $storage_list = [ $src_sid ];
3718 push @$storage_list, $storage if defined($storage);
3719 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3723 running
=> $running,
3726 snapname
=> $snapname,
3732 storage
=> $storage,
3736 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3737 if $opt eq 'efidisk0';
3739 my $newdrive = PVE
::QemuServer
::clone_disk
(
3751 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3753 PVE
::QemuConfig-
>write_config($newid, $newconf);
3757 delete $newconf->{lock};
3759 # do not write pending changes
3760 if (my @changes = keys %{$newconf->{pending
}}) {
3761 my $pending = join(',', @changes);
3762 warn "found pending changes for '$pending', discarding for clone\n";
3763 delete $newconf->{pending
};
3766 PVE
::QemuConfig-
>write_config($newid, $newconf);
3769 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3770 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3771 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3773 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3774 die "Failed to move config to node '$target' - rename failed: $!\n"
3775 if !rename($conffile, $newconffile);
3778 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3781 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3782 sleep 1; # some storage like rbd need to wait before release volume - really?
3784 foreach my $volid (@$newvollist) {
3785 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3789 PVE
::Firewall
::remove_vmfw_conf
($newid);
3791 unlink $conffile; # avoid races -> last thing before die
3793 die "clone failed: $err";
3799 # Aquire exclusive lock lock for $newid
3800 my $lock_target_vm = sub {
3801 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3804 my $lock_source_vm = sub {
3805 # exclusive lock if VM is running - else shared lock is enough;
3807 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3809 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3813 $load_and_check->(); # early checks before forking/locking
3815 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3818 __PACKAGE__-
>register_method({
3819 name
=> 'move_vm_disk',
3820 path
=> '{vmid}/move_disk',
3824 description
=> "Move volume to different storage or to a different VM.",
3826 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3827 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3828 "a disk to another VM, you need the permissions on the target VM as well.",
3829 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3832 additionalProperties
=> 0,
3834 node
=> get_standard_option
('pve-node'),
3835 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3836 'target-vmid' => get_standard_option
('pve-vmid', {
3837 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3842 description
=> "The disk you want to move.",
3843 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3845 storage
=> get_standard_option
('pve-storage-id', {
3846 description
=> "Target storage.",
3847 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3852 description
=> "Target Format.",
3853 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3858 description
=> "Delete the original disk after successful copy. By default the"
3859 ." original disk is kept as unused disk.",
3865 description
=> 'Prevent changes if current configuration file has different SHA1"
3866 ." digest. This can be used to prevent concurrent modifications.',
3871 description
=> "Override I/O bandwidth limit (in KiB/s).",
3875 default => 'move limit from datacenter or storage config',
3879 description
=> "The config key the disk will be moved to on the target VM"
3880 ." (for example, ide0 or scsi1). Default is the source disk key.",
3881 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3884 'target-digest' => {
3886 description
=> 'Prevent changes if the current config file of the target VM has a"
3887 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3895 description
=> "the task ID.",
3900 my $rpcenv = PVE
::RPCEnvironment
::get
();
3901 my $authuser = $rpcenv->get_user();
3903 my $node = extract_param
($param, 'node');
3904 my $vmid = extract_param
($param, 'vmid');
3905 my $target_vmid = extract_param
($param, 'target-vmid');
3906 my $digest = extract_param
($param, 'digest');
3907 my $target_digest = extract_param
($param, 'target-digest');
3908 my $disk = extract_param
($param, 'disk');
3909 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3910 my $storeid = extract_param
($param, 'storage');
3911 my $format = extract_param
($param, 'format');
3913 my $storecfg = PVE
::Storage
::config
();
3915 my $load_and_check_move = sub {
3916 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3917 PVE
::QemuConfig-
>check_lock($conf);
3919 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3921 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3923 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3925 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3926 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3928 my $old_volid = $drive->{file
};
3930 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3931 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3935 die "you can't move to the same storage with same format\n"
3936 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3938 # this only checks snapshots because $disk is passed!
3939 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3945 die "you can't move a disk with snapshots and delete the source\n"
3946 if $snapshotted && $param->{delete};
3948 return ($conf, $drive, $oldstoreid, $snapshotted);
3951 my $move_updatefn = sub {
3952 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3953 my $old_volid = $drive->{file
};
3955 PVE
::Cluster
::log_msg
(
3958 "move disk VM $vmid: move --disk $disk --storage $storeid"
3961 my $running = PVE
::QemuServer
::check_running
($vmid);
3963 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3965 my $newvollist = [];
3971 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3973 warn "moving disk with snapshots, snapshots will not be moved!\n"
3976 my $bwlimit = extract_param
($param, 'bwlimit');
3977 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3979 [$oldstoreid, $storeid],
3985 running
=> $running,
3994 storage
=> $storeid,
3998 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3999 if $disk eq 'efidisk0';
4001 my $newdrive = PVE
::QemuServer
::clone_disk
(
4012 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
4014 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
4016 # convert moved disk to base if part of template
4017 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
4018 if PVE
::QemuConfig-
>is_template($conf);
4020 PVE
::QemuConfig-
>write_config($vmid, $conf);
4022 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
4023 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
4024 eval { mon_cmd
($vmid, "guest-fstrim") };
4028 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
4029 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
4035 foreach my $volid (@$newvollist) {
4036 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
4039 die "storage migration failed: $err";
4042 if ($param->{delete}) {
4044 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
4045 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
4051 my $load_and_check_reassign_configs = sub {
4052 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
4054 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
4055 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
4057 my $source_node = $vmlist->{$vmid}->{node
};
4058 my $target_node = $vmlist->{$target_vmid}->{node
};
4060 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
4061 if $source_node ne $target_node;
4063 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
4064 PVE
::QemuConfig-
>check_lock($source_conf);
4065 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
4066 PVE
::QemuConfig-
>check_lock($target_conf);
4068 die "Can't move disks from or to template VMs\n"
4069 if ($source_conf->{template
} || $target_conf->{template
});
4072 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
4073 die "VM ${vmid}: $@" if $@;
4076 if ($target_digest) {
4077 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
4078 die "VM ${target_vmid}: $@" if $@;
4081 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
4083 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
4084 if $target_conf->{$target_disk};
4086 my $drive = PVE
::QemuServer
::parse_drive
(
4088 $source_conf->{$disk},
4090 die "failed to parse source disk - $@\n" if !$drive;
4092 my $source_volid = $drive->{file
};
4094 die "disk '${disk}' has no associated volume\n" if !$source_volid;
4095 die "CD drive contents can't be moved to another VM\n"
4096 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
4098 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
4099 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
4101 die "Can't move disk used by a snapshot to another VM\n"
4102 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
4103 die "Storage does not support moving of this disk to another VM\n"
4104 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
4105 die "Cannot move disk to another VM while the source VM is running - detach first\n"
4106 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
4108 # now re-parse using target disk slot format
4109 if ($target_disk =~ /^unused\d+$/) {
4110 $drive = PVE
::QemuServer
::parse_drive
(
4115 $drive = PVE
::QemuServer
::parse_drive
(
4117 $source_conf->{$disk},
4120 die "failed to parse source disk for target disk format - $@\n" if !$drive;
4122 my $repl_conf = PVE
::ReplicationConfig-
>new();
4123 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
4124 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4125 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
4126 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
4129 return ($source_conf, $target_conf, $drive);
4134 print STDERR
"$msg\n";
4137 my $disk_reassignfn = sub {
4138 return PVE
::QemuConfig-
>lock_config($vmid, sub {
4139 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
4140 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
4142 my $source_volid = $drive->{file
};
4144 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
4145 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
4147 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4149 my $new_volid = PVE
::Storage
::rename_volume
(
4155 $drive->{file
} = $new_volid;
4157 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
4158 if (defined(delete $boot_order->{$disk})) {
4159 print "removing disk '$disk' from boot order config\n";
4160 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
4161 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
4164 delete $source_conf->{$disk};
4165 print "removing disk '${disk}' from VM '${vmid}' config\n";
4166 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
4168 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
4170 if ($target_disk =~ /^unused\d+$/) {
4171 $target_conf->{$target_disk} = $drive_string;
4172 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4177 vmid
=> $target_vmid,
4178 digest
=> $target_digest,
4179 $target_disk => $drive_string,
4185 # remove possible replication snapshots
4186 if (PVE
::Storage
::volume_has_feature
(
4192 PVE
::Replication
::prepare
(
4202 print "Failed to remove replication snapshots on moved disk " .
4203 "'$target_disk'. Manual cleanup could be necessary.\n";
4210 if ($target_vmid && $storeid) {
4211 my $msg = "either set 'storage' or 'target-vmid', but not both";
4212 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4213 } elsif ($target_vmid) {
4214 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4215 if $authuser ne 'root@pam';
4217 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4218 if $vmid eq $target_vmid;
4220 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4221 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4222 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4224 return $rpcenv->fork_worker(
4226 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4230 } elsif ($storeid) {
4231 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4233 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4234 if $disk =~ m/^unused\d+$/;
4236 $load_and_check_move->(); # early checks before forking/locking
4239 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4242 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4244 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4245 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4249 my $check_vm_disks_local = sub {
4250 my ($storecfg, $vmconf, $vmid) = @_;
4252 my $local_disks = {};
4254 # add some more information to the disks e.g. cdrom
4255 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4256 my ($volid, $attr) = @_;
4258 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4260 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4261 return if $scfg->{shared
};
4263 # The shared attr here is just a special case where the vdisk
4264 # is marked as shared manually
4265 return if $attr->{shared
};
4266 return if $attr->{cdrom
} and $volid eq "none";
4268 if (exists $local_disks->{$volid}) {
4269 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4271 $local_disks->{$volid} = $attr;
4272 # ensure volid is present in case it's needed
4273 $local_disks->{$volid}->{volid
} = $volid;
4277 return $local_disks;
4280 __PACKAGE__-
>register_method({
4281 name
=> 'migrate_vm_precondition',
4282 path
=> '{vmid}/migrate',
4286 description
=> "Get preconditions for migration.",
4288 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4291 additionalProperties
=> 0,
4293 node
=> get_standard_option
('pve-node'),
4294 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4295 target
=> get_standard_option
('pve-node', {
4296 description
=> "Target node.",
4297 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4305 running
=> { type
=> 'boolean' },
4309 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4311 not_allowed_nodes
=> {
4314 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4318 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4320 local_resources
=> {
4322 description
=> "List local resources e.g. pci, usb"
4324 'mapped-resources' => {
4326 description
=> "List of mapped resources e.g. pci, usb"
4333 my $rpcenv = PVE
::RPCEnvironment
::get
();
4335 my $authuser = $rpcenv->get_user();
4337 PVE
::Cluster
::check_cfs_quorum
();
4341 my $vmid = extract_param
($param, 'vmid');
4342 my $target = extract_param
($param, 'target');
4343 my $localnode = PVE
::INotify
::nodename
();
4347 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4348 my $storecfg = PVE
::Storage
::config
();
4351 # try to detect errors early
4352 PVE
::QemuConfig-
>check_lock($vmconf);
4354 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4356 my ($local_resources, $mapped_resources, $missing_mappings_by_node) =
4357 PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4358 delete $missing_mappings_by_node->{$localnode};
4360 # if vm is not running, return target nodes where local storage/mapped devices are available
4361 # for offline migration
4362 if (!$res->{running
}) {
4363 $res->{allowed_nodes
} = [];
4364 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4365 delete $checked_nodes->{$localnode};
4367 foreach my $node (keys %$checked_nodes) {
4368 my $missing_mappings = $missing_mappings_by_node->{$node};
4369 if (scalar($missing_mappings->@*)) {
4370 $checked_nodes->{$node}->{'unavailable-resources'} = $missing_mappings;
4374 if (!defined($checked_nodes->{$node}->{unavailable_storages
})) {
4375 push @{$res->{allowed_nodes
}}, $node;
4379 $res->{not_allowed_nodes
} = $checked_nodes;
4382 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4383 $res->{local_disks
} = [ values %$local_disks ];;
4385 $res->{local_resources
} = $local_resources;
4386 $res->{'mapped-resources'} = $mapped_resources;
4393 __PACKAGE__-
>register_method({
4394 name
=> 'migrate_vm',
4395 path
=> '{vmid}/migrate',
4399 description
=> "Migrate virtual machine. Creates a new migration task.",
4401 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4404 additionalProperties
=> 0,
4406 node
=> get_standard_option
('pve-node'),
4407 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4408 target
=> get_standard_option
('pve-node', {
4409 description
=> "Target node.",
4410 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4414 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4419 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4424 enum
=> ['secure', 'insecure'],
4425 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4428 migration_network
=> {
4429 type
=> 'string', format
=> 'CIDR',
4430 description
=> "CIDR of the (sub) network that is used for migration.",
4433 "with-local-disks" => {
4435 description
=> "Enable live storage migration for local disk",
4438 targetstorage
=> get_standard_option
('pve-targetstorage', {
4439 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4442 description
=> "Override I/O bandwidth limit (in KiB/s).",
4446 default => 'migrate limit from datacenter or storage config',
4452 description
=> "the task ID.",
4457 my $rpcenv = PVE
::RPCEnvironment
::get
();
4458 my $authuser = $rpcenv->get_user();
4460 my $target = extract_param
($param, 'target');
4462 my $localnode = PVE
::INotify
::nodename
();
4463 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4465 PVE
::Cluster
::check_cfs_quorum
();
4467 PVE
::Cluster
::check_node_exists
($target);
4469 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4471 my $vmid = extract_param
($param, 'vmid');
4473 raise_param_exc
({ force
=> "Only root may use this option." })
4474 if $param->{force
} && $authuser ne 'root@pam';
4476 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4477 if $param->{migration_type
} && $authuser ne 'root@pam';
4479 # allow root only until better network permissions are available
4480 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4481 if $param->{migration_network
} && $authuser ne 'root@pam';
4484 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4486 # try to detect errors early
4488 PVE
::QemuConfig-
>check_lock($conf);
4490 if (PVE
::QemuServer
::check_running
($vmid)) {
4491 die "can't migrate running VM without --online\n" if !$param->{online
};
4493 my $repl_conf = PVE
::ReplicationConfig-
>new();
4494 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4495 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4496 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4497 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4498 "target. Use 'force' to override.\n";
4501 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4502 $param->{online
} = 0;
4505 my $storecfg = PVE
::Storage
::config
();
4506 if (my $targetstorage = $param->{targetstorage
}) {
4507 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4508 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4511 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4512 if !defined($storagemap->{identity
});
4514 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4515 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4518 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4519 if $storagemap->{default};
4521 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4522 if $storagemap->{identity
};
4524 $param->{storagemap
} = $storagemap;
4526 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4529 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4534 print "Requesting HA migration for VM $vmid to node $target\n";
4536 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4537 PVE
::Tools
::run_command
($cmd);
4541 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4546 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4550 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4553 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4558 __PACKAGE__-
>register_method({
4559 name
=> 'remote_migrate_vm',
4560 path
=> '{vmid}/remote_migrate',
4564 description
=> "Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!",
4566 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4569 additionalProperties
=> 0,
4571 node
=> get_standard_option
('pve-node'),
4572 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4573 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
4574 'target-endpoint' => get_standard_option
('proxmox-remote', {
4575 description
=> "Remote target endpoint",
4579 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4584 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.",
4588 'target-storage' => get_standard_option
('pve-targetstorage', {
4589 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4592 'target-bridge' => {
4594 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.",
4595 format
=> 'bridge-pair-list',
4598 description
=> "Override I/O bandwidth limit (in KiB/s).",
4602 default => 'migrate limit from datacenter or storage config',
4608 description
=> "the task ID.",
4613 my $rpcenv = PVE
::RPCEnvironment
::get
();
4614 my $authuser = $rpcenv->get_user();
4616 my $source_vmid = extract_param
($param, 'vmid');
4617 my $target_endpoint = extract_param
($param, 'target-endpoint');
4618 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
4620 my $delete = extract_param
($param, 'delete') // 0;
4622 PVE
::Cluster
::check_cfs_quorum
();
4625 my $conf = PVE
::QemuConfig-
>load_config($source_vmid);
4627 PVE
::QemuConfig-
>check_lock($conf);
4629 raise_param_exc
({ vmid
=> "cannot migrate HA-managed VM to remote cluster" })
4630 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
4632 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
4634 # TODO: move this as helper somewhere appropriate?
4636 protocol
=> 'https',
4637 host
=> $remote->{host
},
4638 port
=> $remote->{port
} // 8006,
4639 apitoken
=> $remote->{apitoken
},
4643 if ($fp = $remote->{fingerprint
}) {
4644 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
4647 print "Establishing API connection with remote at '$remote->{host}'\n";
4649 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
4651 if (!defined($fp)) {
4652 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
4653 foreach my $cert (@$cert_info) {
4654 my $filename = $cert->{filename
};
4655 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
4656 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
4658 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
4662 my $repl_conf = PVE
::ReplicationConfig-
>new();
4663 my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1);
4664 die "cannot remote-migrate replicated VM\n" if $is_replicated;
4666 if (PVE
::QemuServer
::check_running
($source_vmid)) {
4667 die "can't migrate running VM without --online\n" if !$param->{online
};
4670 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4671 $param->{online
} = 0;
4674 my $storecfg = PVE
::Storage
::config
();
4675 my $target_storage = extract_param
($param, 'target-storage');
4676 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
4677 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
4680 my $target_bridge = extract_param
($param, 'target-bridge');
4681 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
4682 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
4685 die "remote migration requires explicit storage mapping!\n"
4686 if $storagemap->{identity
};
4688 $param->{storagemap
} = $storagemap;
4689 $param->{bridgemap
} = $bridgemap;
4690 $param->{remote
} = {
4691 conn
=> $conn_args, # re-use fingerprint for tunnel
4692 client
=> $api_client,
4693 vmid
=> $target_vmid,
4695 $param->{migration_type
} = 'websocket';
4696 $param->{'with-local-disks'} = 1;
4697 $param->{delete} = $delete if $delete;
4699 my $cluster_status = $api_client->get("/cluster/status");
4701 foreach my $entry (@$cluster_status) {
4702 next if $entry->{type
} ne 'node';
4703 if ($entry->{local}) {
4704 $target_node = $entry->{name
};
4709 die "couldn't determine endpoint's node name\n"
4710 if !defined($target_node);
4713 PVE
::QemuMigrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
4717 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
4720 return $rpcenv->fork_worker('qmigrate', $source_vmid, $authuser, $worker);
4723 __PACKAGE__-
>register_method({
4725 path
=> '{vmid}/monitor',
4729 description
=> "Execute QEMU monitor commands.",
4731 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4732 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4735 additionalProperties
=> 0,
4737 node
=> get_standard_option
('pve-node'),
4738 vmid
=> get_standard_option
('pve-vmid'),
4741 description
=> "The monitor command.",
4745 returns
=> { type
=> 'string'},
4749 my $rpcenv = PVE
::RPCEnvironment
::get
();
4750 my $authuser = $rpcenv->get_user();
4753 my $command = shift;
4754 return $command =~ m/^\s*info(\s+|$)/
4755 || $command =~ m/^\s*help\s*$/;
4758 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4759 if !&$is_ro($param->{command
});
4761 my $vmid = $param->{vmid
};
4763 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4767 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4769 $res = "ERROR: $@" if $@;
4774 __PACKAGE__-
>register_method({
4775 name
=> 'resize_vm',
4776 path
=> '{vmid}/resize',
4780 description
=> "Extend volume size.",
4782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4785 additionalProperties
=> 0,
4787 node
=> get_standard_option
('pve-node'),
4788 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4789 skiplock
=> get_standard_option
('skiplock'),
4792 description
=> "The disk you want to resize.",
4793 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4797 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4798 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.",
4802 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4810 description
=> "the task ID.",
4815 my $rpcenv = PVE
::RPCEnvironment
::get
();
4817 my $authuser = $rpcenv->get_user();
4819 my $node = extract_param
($param, 'node');
4821 my $vmid = extract_param
($param, 'vmid');
4823 my $digest = extract_param
($param, 'digest');
4825 my $disk = extract_param
($param, 'disk');
4827 my $sizestr = extract_param
($param, 'size');
4829 my $skiplock = extract_param
($param, 'skiplock');
4830 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4831 if $skiplock && $authuser ne 'root@pam';
4833 my $storecfg = PVE
::Storage
::config
();
4835 my $updatefn = sub {
4837 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4839 die "checksum missmatch (file change by other user?)\n"
4840 if $digest && $digest ne $conf->{digest
};
4841 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4843 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4845 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4847 my (undef, undef, undef, undef, undef, undef, $format) =
4848 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4850 my $volid = $drive->{file
};
4852 die "disk '$disk' has no associated volume\n" if !$volid;
4854 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4856 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4858 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4860 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4861 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4863 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4865 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4866 my ($ext, $newsize, $unit) = ($1, $2, $4);
4869 $newsize = $newsize * 1024;
4870 } elsif ($unit eq 'M') {
4871 $newsize = $newsize * 1024 * 1024;
4872 } elsif ($unit eq 'G') {
4873 $newsize = $newsize * 1024 * 1024 * 1024;
4874 } elsif ($unit eq 'T') {
4875 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4878 $newsize += $size if $ext;
4879 $newsize = int($newsize);
4881 die "shrinking disks is not supported\n" if $newsize < $size;
4883 return if $size == $newsize;
4885 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4887 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4889 $drive->{size
} = $newsize;
4890 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4892 PVE
::QemuConfig-
>write_config($vmid, $conf);
4896 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4899 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
4902 __PACKAGE__-
>register_method({
4903 name
=> 'snapshot_list',
4904 path
=> '{vmid}/snapshot',
4906 description
=> "List all snapshots.",
4908 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4911 protected
=> 1, # qemu pid files are only readable by root
4913 additionalProperties
=> 0,
4915 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4916 node
=> get_standard_option
('pve-node'),
4925 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4929 description
=> "Snapshot includes RAM.",
4934 description
=> "Snapshot description.",
4938 description
=> "Snapshot creation time",
4940 renderer
=> 'timestamp',
4944 description
=> "Parent snapshot identifier.",
4950 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4955 my $vmid = $param->{vmid
};
4957 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4958 my $snaphash = $conf->{snapshots
} || {};
4962 foreach my $name (keys %$snaphash) {
4963 my $d = $snaphash->{$name};
4966 snaptime
=> $d->{snaptime
} || 0,
4967 vmstate
=> $d->{vmstate
} ?
1 : 0,
4968 description
=> $d->{description
} || '',
4970 $item->{parent
} = $d->{parent
} if $d->{parent
};
4971 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4975 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4978 digest
=> $conf->{digest
},
4979 running
=> $running,
4980 description
=> "You are here!",
4982 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4984 push @$res, $current;
4989 __PACKAGE__-
>register_method({
4991 path
=> '{vmid}/snapshot',
4995 description
=> "Snapshot a VM.",
4997 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5000 additionalProperties
=> 0,
5002 node
=> get_standard_option
('pve-node'),
5003 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5004 snapname
=> get_standard_option
('pve-snapshot-name'),
5008 description
=> "Save the vmstate",
5013 description
=> "A textual description or comment.",
5019 description
=> "the task ID.",
5024 my $rpcenv = PVE
::RPCEnvironment
::get
();
5026 my $authuser = $rpcenv->get_user();
5028 my $node = extract_param
($param, 'node');
5030 my $vmid = extract_param
($param, 'vmid');
5032 my $snapname = extract_param
($param, 'snapname');
5034 die "unable to use snapshot name 'current' (reserved name)\n"
5035 if $snapname eq 'current';
5037 die "unable to use snapshot name 'pending' (reserved name)\n"
5038 if lc($snapname) eq 'pending';
5041 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
5042 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
5043 $param->{description
});
5046 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
5049 __PACKAGE__-
>register_method({
5050 name
=> 'snapshot_cmd_idx',
5051 path
=> '{vmid}/snapshot/{snapname}',
5058 additionalProperties
=> 0,
5060 vmid
=> get_standard_option
('pve-vmid'),
5061 node
=> get_standard_option
('pve-node'),
5062 snapname
=> get_standard_option
('pve-snapshot-name'),
5071 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
5078 push @$res, { cmd
=> 'rollback' };
5079 push @$res, { cmd
=> 'config' };
5084 __PACKAGE__-
>register_method({
5085 name
=> 'update_snapshot_config',
5086 path
=> '{vmid}/snapshot/{snapname}/config',
5090 description
=> "Update snapshot metadata.",
5092 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5095 additionalProperties
=> 0,
5097 node
=> get_standard_option
('pve-node'),
5098 vmid
=> get_standard_option
('pve-vmid'),
5099 snapname
=> get_standard_option
('pve-snapshot-name'),
5103 description
=> "A textual description or comment.",
5107 returns
=> { type
=> 'null' },
5111 my $rpcenv = PVE
::RPCEnvironment
::get
();
5113 my $authuser = $rpcenv->get_user();
5115 my $vmid = extract_param
($param, 'vmid');
5117 my $snapname = extract_param
($param, 'snapname');
5119 return if !defined($param->{description
});
5121 my $updatefn = sub {
5123 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5125 PVE
::QemuConfig-
>check_lock($conf);
5127 my $snap = $conf->{snapshots
}->{$snapname};
5129 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5131 $snap->{description
} = $param->{description
} if defined($param->{description
});
5133 PVE
::QemuConfig-
>write_config($vmid, $conf);
5136 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
5141 __PACKAGE__-
>register_method({
5142 name
=> 'get_snapshot_config',
5143 path
=> '{vmid}/snapshot/{snapname}/config',
5146 description
=> "Get snapshot configuration",
5148 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
5151 additionalProperties
=> 0,
5153 node
=> get_standard_option
('pve-node'),
5154 vmid
=> get_standard_option
('pve-vmid'),
5155 snapname
=> get_standard_option
('pve-snapshot-name'),
5158 returns
=> { type
=> "object" },
5162 my $rpcenv = PVE
::RPCEnvironment
::get
();
5164 my $authuser = $rpcenv->get_user();
5166 my $vmid = extract_param
($param, 'vmid');
5168 my $snapname = extract_param
($param, 'snapname');
5170 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5172 my $snap = $conf->{snapshots
}->{$snapname};
5174 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5179 __PACKAGE__-
>register_method({
5181 path
=> '{vmid}/snapshot/{snapname}/rollback',
5185 description
=> "Rollback VM state to specified snapshot.",
5187 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
5190 additionalProperties
=> 0,
5192 node
=> get_standard_option
('pve-node'),
5193 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5194 snapname
=> get_standard_option
('pve-snapshot-name'),
5197 description
=> "Whether the VM should get started after rolling back successfully."
5198 . " (Note: VMs will be automatically started if the snapshot includes RAM.)",
5206 description
=> "the task ID.",
5211 my $rpcenv = PVE
::RPCEnvironment
::get
();
5213 my $authuser = $rpcenv->get_user();
5215 my $node = extract_param
($param, 'node');
5217 my $vmid = extract_param
($param, 'vmid');
5219 my $snapname = extract_param
($param, 'snapname');
5222 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
5223 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
5225 if ($param->{start
} && !PVE
::QemuServer
::Helpers
::vm_running_locally
($vmid)) {
5226 PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node });
5231 # hold migration lock, this makes sure that nobody create replication snapshots
5232 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
5235 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
5238 __PACKAGE__-
>register_method({
5239 name
=> 'delsnapshot',
5240 path
=> '{vmid}/snapshot/{snapname}',
5244 description
=> "Delete a VM snapshot.",
5246 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5249 additionalProperties
=> 0,
5251 node
=> get_standard_option
('pve-node'),
5252 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5253 snapname
=> get_standard_option
('pve-snapshot-name'),
5257 description
=> "For removal from config file, even if removing disk snapshots fails.",
5263 description
=> "the task ID.",
5268 my $rpcenv = PVE
::RPCEnvironment
::get
();
5270 my $authuser = $rpcenv->get_user();
5272 my $node = extract_param
($param, 'node');
5274 my $vmid = extract_param
($param, 'vmid');
5276 my $snapname = extract_param
($param, 'snapname');
5279 my $do_delete = sub {
5281 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
5282 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
5286 if ($param->{force
}) {
5289 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
5291 die $err if $lock_obtained;
5292 die "Failed to obtain guest migration lock - replication running?\n";
5297 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
5300 __PACKAGE__-
>register_method({
5302 path
=> '{vmid}/template',
5306 description
=> "Create a Template.",
5308 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
5309 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
5312 additionalProperties
=> 0,
5314 node
=> get_standard_option
('pve-node'),
5315 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
5319 description
=> "If you want to convert only 1 disk to base image.",
5320 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
5327 description
=> "the task ID.",
5332 my $rpcenv = PVE
::RPCEnvironment
::get
();
5334 my $authuser = $rpcenv->get_user();
5336 my $node = extract_param
($param, 'node');
5338 my $vmid = extract_param
($param, 'vmid');
5340 my $disk = extract_param
($param, 'disk');
5342 my $load_and_check = sub {
5343 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5345 PVE
::QemuConfig-
>check_lock($conf);
5347 die "unable to create template, because VM contains snapshots\n"
5348 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
5350 die "you can't convert a template to a template\n"
5351 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
5353 die "you can't convert a VM to template if VM is running\n"
5354 if PVE
::QemuServer
::check_running
($vmid);
5359 $load_and_check->();
5362 PVE
::QemuConfig-
>lock_config($vmid, sub {
5363 my $conf = $load_and_check->();
5365 $conf->{template
} = 1;
5366 PVE
::QemuConfig-
>write_config($vmid, $conf);
5368 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5372 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5375 __PACKAGE__-
>register_method({
5376 name
=> 'cloudinit_generated_config_dump',
5377 path
=> '{vmid}/cloudinit/dump',
5380 description
=> "Get automatically generated cloudinit config.",
5382 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5385 additionalProperties
=> 0,
5387 node
=> get_standard_option
('pve-node'),
5388 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5390 description
=> 'Config type.',
5392 enum
=> ['user', 'network', 'meta'],
5402 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5404 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});
5407 __PACKAGE__-
>register_method({
5409 path
=> '{vmid}/mtunnel',
5412 description
=> 'Migration tunnel endpoint - only for internal use by VM migration.',
5416 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
5417 ['perm', '/', [ 'Sys.Incoming' ]],
5419 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
5420 " on '/'. Further permission checks happen during the actual migration.",
5423 additionalProperties
=> 0,
5425 node
=> get_standard_option
('pve-node'),
5426 vmid
=> get_standard_option
('pve-vmid'),
5429 format
=> 'pve-storage-id-list',
5431 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
5435 format
=> 'pve-bridge-id-list',
5437 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
5442 additionalProperties
=> 0,
5444 upid
=> { type
=> 'string' },
5445 ticket
=> { type
=> 'string' },
5446 socket => { type
=> 'string' },
5452 my $rpcenv = PVE
::RPCEnvironment
::get
();
5453 my $authuser = $rpcenv->get_user();
5455 my $node = extract_param
($param, 'node');
5456 my $vmid = extract_param
($param, 'vmid');
5458 my $storages = extract_param
($param, 'storages');
5459 my $bridges = extract_param
($param, 'bridges');
5461 my $nodename = PVE
::INotify
::nodename
();
5463 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5464 if $node ne 'localhost' && $node ne $nodename;
5468 my $storecfg = PVE
::Storage
::config
();
5469 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
5470 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
5473 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
5474 PVE
::Network
::read_bridge_mtu
($bridge);
5477 PVE
::Cluster
::check_cfs_quorum
();
5479 my $lock = 'create';
5480 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, 0, $lock); };
5482 raise_param_exc
({ vmid
=> "unable to create empty VM config - $@"})
5487 storecfg
=> PVE
::Storage
::config
(),
5492 my $run_locked = sub {
5493 my ($code, $params) = @_;
5494 return PVE
::QemuConfig-
>lock_config($state->{vmid
}, sub {
5495 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5497 $state->{conf
} = $conf;
5499 die "Encountered wrong lock - aborting mtunnel command handling.\n"
5500 if $state->{lock} && !PVE
::QemuConfig-
>has_lock($conf, $state->{lock});
5502 return $code->($params);
5510 description
=> 'Full VM config, adapted for target cluster/node',
5512 'firewall-config' => {
5514 description
=> 'VM firewall config',
5519 format
=> PVE
::JSONSchema
::get_standard_option
('pve-qm-image-format'),
5522 format
=> 'pve-storage-id',
5526 description
=> 'parsed drive information without volid and format',
5532 description
=> 'params passed to vm_start_nolock',
5536 description
=> 'migrate_opts passed to vm_start_nolock',
5542 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
5548 description
=> 'remove VM config and disks, aborting migration',
5552 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
5553 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
5554 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
5557 my $cmd_handlers = {
5559 # compared against other end's version
5560 # bump/reset for breaking changes
5561 # bump/bump for opt-in changes
5563 api
=> $PVE::QemuMigrate
::WS_TUNNEL_VERSION
,
5570 # parse and write out VM FW config if given
5571 if (my $fw_conf = $params->{'firewall-config'}) {
5572 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
5579 ipset_comments
=> {},
5581 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
5583 # TODO: add flag for strict parsing?
5584 # TODO: add import sub that does all this given raw content?
5585 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
5586 $vmfw_conf->{vmid
} = $state->{vmid
};
5587 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
5589 $state->{cleanup
}->{fw
} = 1;
5592 my $conf_fn = "incoming/qemu-server/$state->{vmid}.conf";
5593 my $new_conf = PVE
::QemuServer
::parse_vm_config
($conf_fn, $params->{conf
}, 1);
5594 delete $new_conf->{lock};
5595 delete $new_conf->{digest
};
5597 # TODO handle properly?
5598 delete $new_conf->{snapshots
};
5599 delete $new_conf->{parent
};
5600 delete $new_conf->{pending
};
5602 # not handled by update_vm_api
5603 my $vmgenid = delete $new_conf->{vmgenid
};
5604 my $meta = delete $new_conf->{meta
};
5605 my $cloudinit = delete $new_conf->{cloudinit
}; # this is informational only
5606 $new_conf->{skip_cloud_init
} = 1; # re-use image from source side
5608 $new_conf->{vmid
} = $state->{vmid
};
5609 $new_conf->{node
} = $node;
5611 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, 'create');
5614 $update_vm_api->($new_conf, 1);
5617 # revert to locked previous config
5618 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5619 $conf->{lock} = 'create';
5620 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5625 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5626 $conf->{lock} = 'migrate';
5627 $conf->{vmgenid
} = $vmgenid if defined($vmgenid);
5628 $conf->{meta
} = $meta if defined($meta);
5629 $conf->{cloudinit
} = $cloudinit if defined($cloudinit);
5630 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5632 $state->{lock} = 'migrate';
5638 return PVE
::StorageTunnel
::handle_bwlimit
($params);
5643 my $format = $params->{format
};
5644 my $storeid = $params->{storage
};
5645 my $drive = $params->{drive
};
5647 $check_storage_access_migrate->($rpcenv, $authuser, $state->{storecfg
}, $storeid, $node);
5650 default => $storeid,
5653 my $source_volumes = {
5664 my $res = PVE
::QemuServer
::vm_migrate_alloc_nbd_disks
($state->{storecfg
}, $state->{vmid
}, $source_volumes, $storagemap);
5665 if (defined($res->{disk
})) {
5666 $state->{cleanup
}->{volumes
}->{$res->{disk
}->{volid
}} = 1;
5667 return $res->{disk
};
5669 die "failed to allocate NBD disk..\n";
5672 'disk-import' => sub {
5675 $check_storage_access_migrate->(
5683 $params->{unix
} = "/run/qemu-server/$state->{vmid}.storage";
5685 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
5687 'query-disk-import' => sub {
5690 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
5695 my $info = PVE
::QemuServer
::vm_start_nolock
(
5699 $params->{start_params
},
5700 $params->{migrate_opts
},
5704 if ($info->{migrate
}->{proto
} ne 'unix') {
5705 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5706 die "migration over non-UNIX sockets not possible\n";
5709 my $socket = $info->{migrate
}->{addr
};
5710 chown $state->{socket_uid
}, -1, $socket;
5711 $state->{sockets
}->{$socket} = 1;
5713 my $unix_sockets = $info->{migrate
}->{unix_sockets
};
5714 foreach my $socket (@$unix_sockets) {
5715 chown $state->{socket_uid
}, -1, $socket;
5716 $state->{sockets
}->{$socket} = 1;
5721 if (PVE
::QemuServer
::qga_check_running
($state->{vmid
})) {
5722 eval { mon_cmd
($state->{vmid
}, "guest-fstrim") };
5723 warn "fstrim failed: $@\n" if $@;
5728 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5732 PVE
::QemuServer
::nbd_stop
($state->{vmid
});
5736 if (PVE
::QemuServer
::Helpers
::vm_running_locally
($state->{vmid
})) {
5737 PVE
::QemuServer
::vm_resume
($state->{vmid
}, 1, 1);
5739 die "VM $state->{vmid} not running\n";
5744 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, $state->{lock});
5745 delete $state->{lock};
5751 my $path = $params->{path
};
5753 die "Not allowed to generate ticket for unknown socket '$path'\n"
5754 if !defined($state->{sockets
}->{$path});
5756 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
5761 if ($params->{cleanup
}) {
5762 if ($state->{cleanup
}->{fw
}) {
5763 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
5766 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
5767 print "freeing volume '$volid' as part of cleanup\n";
5768 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
5772 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5775 print "switching to exit-mode, waiting for client to disconnect\n";
5782 my $socket_addr = "/run/qemu-server/$state->{vmid}.mtunnel";
5783 unlink $socket_addr;
5785 $state->{socket} = IO
::Socket
::UNIX-
>new(
5786 Type
=> SOCK_STREAM
(),
5787 Local
=> $socket_addr,
5791 $state->{socket_uid
} = getpwnam('www-data')
5792 or die "Failed to resolve user 'www-data' to numeric UID\n";
5793 chown $state->{socket_uid
}, -1, $socket_addr;
5796 print "mtunnel started\n";
5798 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
5800 warn "Failed to accept tunnel connection - $@\n";
5802 warn "Removing tunnel socket..\n";
5803 unlink $state->{socket};
5805 warn "Removing temporary VM config..\n";
5807 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5810 die "Exiting mtunnel\n";
5813 $state->{conn
} = $conn;
5815 my $reply_err = sub {
5818 my $reply = JSON
::encode_json
({
5819 success
=> JSON
::false
,
5822 $conn->print("$reply\n");
5826 my $reply_ok = sub {
5829 $res->{success
} = JSON
::true
;
5830 my $reply = JSON
::encode_json
($res);
5831 $conn->print("$reply\n");
5835 while (my $line = <$conn>) {
5838 # untaint, we validate below if needed
5839 ($line) = $line =~ /^(.*)$/;
5840 my $parsed = eval { JSON
::decode_json
($line) };
5842 $reply_err->("failed to parse command - $@");
5846 my $cmd = delete $parsed->{cmd
};
5847 if (!defined($cmd)) {
5848 $reply_err->("'cmd' missing");
5849 } elsif ($state->{exit}) {
5850 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
5852 } elsif (my $handler = $cmd_handlers->{$cmd}) {
5853 print "received command '$cmd'\n";
5855 if ($cmd_desc->{$cmd}) {
5856 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
5860 my $res = $run_locked->($handler, $parsed);
5863 $reply_err->("failed to handle '$cmd' command - $@")
5866 $reply_err->("unknown command '$cmd' given");
5870 if ($state->{exit}) {
5871 print "mtunnel exited\n";
5873 die "mtunnel exited unexpectedly\n";
5877 my $socket_addr = "/run/qemu-server/$vmid.mtunnel";
5878 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
5879 my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);
5884 socket => $socket_addr,
5888 __PACKAGE__-
>register_method({
5889 name
=> 'mtunnelwebsocket',
5890 path
=> '{vmid}/mtunnelwebsocket',
5893 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.",
5894 user
=> 'all', # check inside
5896 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
5898 additionalProperties
=> 0,
5900 node
=> get_standard_option
('pve-node'),
5901 vmid
=> get_standard_option
('pve-vmid'),
5904 description
=> "unix socket to forward to",
5908 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
5915 port
=> { type
=> 'string', optional
=> 1 },
5916 socket => { type
=> 'string', optional
=> 1 },
5922 my $rpcenv = PVE
::RPCEnvironment
::get
();
5923 my $authuser = $rpcenv->get_user();
5925 my $nodename = PVE
::INotify
::nodename
();
5926 my $node = extract_param
($param, 'node');
5928 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5929 if $node ne 'localhost' && $node ne $nodename;
5931 my $vmid = $param->{vmid
};
5933 PVE
::QemuConfig-
>load_config($vmid);
5935 my $socket = $param->{socket};
5936 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
5938 return { socket => $socket };