1 package PVE
::API2
::Qemu
;
12 use Crypt
::OpenSSL
::Random
;
13 use Socket
qw(SOCK_STREAM);
15 use PVE
::APIClient
::LWP
;
17 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
20 use PVE
::Tools
qw(extract_param);
21 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
23 use PVE
::JSONSchema
qw(get_standard_option);
25 use PVE
::ReplicationConfig
;
26 use PVE
::GuestHelpers
qw(assert_tag_permissions);
29 use PVE
::QemuServer
::Cloudinit
;
30 use PVE
::QemuServer
::CPUConfig
;
31 use PVE
::QemuServer
::Drive
;
32 use PVE
::QemuServer
::ImportDisk
;
33 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
34 use PVE
::QemuServer
::Machine
;
35 use PVE
::QemuServer
::Memory
qw(get_current_memory);
36 use PVE
::QemuServer
::PCI
;
37 use PVE
::QemuServer
::USB
;
39 use PVE
::RPCEnvironment
;
40 use PVE
::AccessControl
;
44 use PVE
::API2
::Firewall
::VM
;
45 use PVE
::API2
::Qemu
::Agent
;
46 use PVE
::VZDump
::Plugin
;
47 use PVE
::DataCenterConfig
;
50 use PVE
::StorageTunnel
;
53 if (!$ENV{PVE_GENERATING_DOCS
}) {
54 require PVE
::HA
::Env
::PVE2
;
55 import PVE
::HA
::Env
::PVE2
;
56 require PVE
::HA
::Config
;
57 import PVE
::HA
::Config
;
61 use base
qw(PVE::RESTHandler);
63 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
65 my $resolve_cdrom_alias = sub {
68 if (my $value = $param->{cdrom
}) {
69 $value .= ",media=cdrom" if $value !~ m/media=/;
70 $param->{ide2
} = $value;
71 delete $param->{cdrom
};
75 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
76 my $foreach_volume_with_alloc = sub {
77 my ($param, $func) = @_;
79 for my $opt (sort keys $param->%*) {
80 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
82 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
85 $func->($opt, $drive);
89 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
91 my $check_drive_param = sub {
92 my ($param, $storecfg, $extra_checks) = @_;
94 for my $opt (sort keys $param->%*) {
95 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
97 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
98 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
100 if ($drive->{'import-from'}) {
101 if ($drive->{file
} !~ $NEW_DISK_RE || $3 != 0) {
103 $opt => "'import-from' requires special syntax - ".
104 "use <storage ID>:0,import-from=<source>",
108 if ($opt eq 'efidisk0') {
109 for my $required (qw(efitype pre-enrolled-keys)) {
110 if (!defined($drive->{$required})) {
112 $opt => "need to specify '$required' when using 'import-from'",
116 } elsif ($opt eq 'tpmstate0') {
117 raise_param_exc
({ $opt => "need to specify 'version' when using 'import-from'" })
118 if !defined($drive->{version
});
122 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
124 $extra_checks->($drive) if $extra_checks;
126 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive, 1);
130 my $check_storage_access = sub {
131 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
133 $foreach_volume_with_alloc->($settings, sub {
134 my ($ds, $drive) = @_;
136 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
138 my $volid = $drive->{file
};
139 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
141 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
143 } elsif ($isCDROM && ($volid eq 'cdrom')) {
144 $rpcenv->check($authuser, "/", ['Sys.Console']);
145 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
146 my ($storeid, $size) = ($2 || $default_storage, $3);
147 die "no storage ID specified (and no default storage)\n" if !$storeid;
148 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
149 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
150 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
151 if !$scfg->{content
}->{images
};
153 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
155 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
156 raise_param_exc
({ $ds => "content type needs to be 'images' or 'iso'" })
157 if $vtype ne 'images' && $vtype ne 'iso';
161 if (my $src_image = $drive->{'import-from'}) {
163 if (PVE
::Storage
::parse_volume_id
($src_image, 1)) { # PVE-managed volume
164 (my $vtype, undef, $src_vmid) = PVE
::Storage
::parse_volname
($storecfg, $src_image);
165 raise_param_exc
({ $ds => "$src_image has wrong type '$vtype' - not an image" })
166 if $vtype ne 'images';
169 if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()
170 $rpcenv->check($authuser, "/vms/${src_vmid}", ['VM.Clone']);
172 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $src_image);
177 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
178 if defined($settings->{vmstatestorage
});
181 my $check_storage_access_clone = sub {
182 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
186 PVE
::QemuConfig-
>foreach_volume($conf, sub {
187 my ($ds, $drive) = @_;
189 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
191 my $volid = $drive->{file
};
193 return if !$volid || $volid eq 'none';
196 if ($volid eq 'cdrom') {
197 $rpcenv->check($authuser, "/", ['Sys.Console']);
199 # we simply allow access
200 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
201 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
202 $sharedvm = 0 if !$scfg->{shared
};
206 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
207 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
208 $sharedvm = 0 if !$scfg->{shared
};
210 $sid = $storage if $storage;
211 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
215 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
216 if defined($conf->{vmstatestorage
});
221 my $check_storage_access_migrate = sub {
222 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
224 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
226 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
228 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
229 die "storage '$storage' does not support vm images\n"
230 if !$scfg->{content
}->{images
};
233 my $import_from_volid = sub {
234 my ($storecfg, $src_volid, $dest_info, $vollist) = @_;
236 die "could not get size of $src_volid\n"
237 if !PVE
::Storage
::volume_size_info
($storecfg, $src_volid, 10);
239 die "cannot import from cloudinit disk\n"
240 if PVE
::QemuServer
::Drive
::drive_is_cloudinit
({ file
=> $src_volid });
242 my $src_vmid = (PVE
::Storage
::parse_volname
($storecfg, $src_volid))[2];
244 my $src_vm_state = sub {
245 my $exists = $src_vmid && PVE
::Cluster
::get_vmlist
()->{ids
}->{$src_vmid} ?
1 : 0;
249 eval { PVE
::QemuConfig
::assert_config_exists_on_node
($src_vmid); };
250 die "owner VM $src_vmid not on local node\n" if $@;
251 $runs = PVE
::QemuServer
::Helpers
::vm_running_locally
($src_vmid) || 0;
254 return ($exists, $runs);
257 my ($src_vm_exists, $running) = $src_vm_state->();
259 die "cannot import from '$src_volid' - full clone feature is not supported\n"
260 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $src_volid, undef, $running);
263 my ($src_vm_exists_now, $running_now) = $src_vm_state->();
265 die "owner VM $src_vmid changed state unexpectedly\n"
266 if $src_vm_exists_now != $src_vm_exists || $running_now != $running;
268 my $src_conf = $src_vm_exists_now ? PVE
::QemuConfig-
>load_config($src_vmid) : {};
270 my $src_drive = { file
=> $src_volid };
272 PVE
::QemuConfig-
>foreach_volume($src_conf, sub {
273 my ($ds, $drive) = @_;
275 return if $src_drivename;
277 if ($drive->{file
} eq $src_volid) {
279 $src_drivename = $ds;
285 running
=> $running_now,
286 drivename
=> $src_drivename,
291 my ($src_storeid) = PVE
::Storage
::parse_volume_id
($src_volid);
293 return PVE
::QemuServer
::clone_disk
(
302 PVE
::Storage
::get_bandwidth_limit
('clone', [$src_storeid, $dest_info->{storage
}]),
308 $cloned = PVE
::QemuConfig-
>lock_config_full($src_vmid, 30, $clonefn);
309 } elsif ($src_vmid) {
310 $cloned = PVE
::QemuConfig-
>lock_config_shared($src_vmid, 30, $clonefn);
312 $cloned = $clonefn->();
315 return $cloned->@{qw(file size)};
318 # Note: $pool is only needed when creating a VM, because pool permissions
319 # are automatically inherited if VM already exists inside a pool.
320 my $create_disks = sub {
321 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
328 my ($ds, $disk) = @_;
330 my $volid = $disk->{file
};
331 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
333 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
334 delete $disk->{size
};
335 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
336 } elsif (defined($volname) && $volname eq 'cloudinit') {
337 $storeid = $storeid // $default_storage;
338 die "no storage ID specified (and no default storage)\n" if !$storeid;
341 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
342 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
343 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
345 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
348 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
349 my $name = "vm-$vmid-cloudinit";
353 $fmt = $disk->{format
} // "qcow2";
356 $fmt = $disk->{format
} // "raw";
359 # Initial disk created with 4 MB and aligned to 4MB on regeneration
360 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
361 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
362 $disk->{file
} = $volid;
363 $disk->{media
} = 'cdrom';
364 push @$vollist, $volid;
365 delete $disk->{format
}; # no longer needed
366 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
367 print "$ds: successfully created disk '$res->{$ds}'\n";
368 } elsif ($volid =~ $NEW_DISK_RE) {
369 my ($storeid, $size) = ($2 || $default_storage, $3);
370 die "no storage ID specified (and no default storage)\n" if !$storeid;
372 if (my $source = delete $disk->{'import-from'}) {
375 if (PVE
::Storage
::parse_volume_id
($source, 1)) { # PVE-managed volume
380 format
=> $disk->{format
},
383 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf, $disk)
384 if $ds eq 'efidisk0';
386 ($dst_volid, $size) = eval {
387 $import_from_volid->($storecfg, $source, $dest_info, $vollist);
389 die "cannot import from '$source' - $@" if $@;
391 $source = PVE
::Storage
::abs_filesystem_path
($storecfg, $source, 1);
392 $size = PVE
::Storage
::file_size_info
($source);
393 die "could not get file size of $source\n" if !$size;
395 (undef, $dst_volid) = PVE
::QemuServer
::ImportDisk
::do_import
(
401 format
=> $disk->{format
},
402 'skip-config-update' => 1,
405 push @$vollist, $dst_volid;
408 $disk->{file
} = $dst_volid;
409 $disk->{size
} = $size;
410 delete $disk->{format
}; # no longer needed
411 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
413 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
414 my $fmt = $disk->{format
} || $defformat;
416 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
419 if ($ds eq 'efidisk0') {
420 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
421 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
422 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
423 } elsif ($ds eq 'tpmstate0') {
424 # swtpm can only use raw volumes, and uses a fixed size
425 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
426 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
428 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
430 push @$vollist, $volid;
431 $disk->{file
} = $volid;
432 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
433 delete $disk->{format
}; # no longer needed
434 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
437 print "$ds: successfully created disk '$res->{$ds}'\n";
439 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
441 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
442 die "cannot use volume $volid - content type needs to be 'images' or 'iso'"
443 if $vtype ne 'images' && $vtype ne 'iso';
445 if (PVE
::QemuServer
::Drive
::drive_is_cloudinit
($disk)) {
447 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
448 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
449 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
451 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
456 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
458 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
459 die "volume $volid does not exist\n" if !$size;
460 $disk->{size
} = $size;
462 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
466 eval { $foreach_volume_with_alloc->($settings, $code); };
468 # free allocated images on error
470 syslog
('err', "VM $vmid creating disks failed");
471 foreach my $volid (@$vollist) {
472 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
478 return ($vollist, $res);
481 my $check_cpu_model_access = sub {
482 my ($rpcenv, $authuser, $new, $existing) = @_;
484 return if !defined($new->{cpu
});
486 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
487 return if !$cpu || !$cpu->{cputype
}; # always allow default
488 my $cputype = $cpu->{cputype
};
490 if ($existing && $existing->{cpu
}) {
491 # changing only other settings doesn't require permissions for CPU model
492 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
493 return if $existingCpu->{cputype
} eq $cputype;
496 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
497 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
512 my $memoryoptions = {
518 my $hwtypeoptions = {
531 my $generaloptions = {
538 'migrate_downtime' => 1,
539 'migrate_speed' => 1,
551 my $vmpoweroptions = {
558 'vmstatestorage' => 1,
561 my $cloudinitoptions = {
572 my $check_vm_create_serial_perm = sub {
573 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
575 return 1 if $authuser eq 'root@pam';
577 foreach my $opt (keys %{$param}) {
578 next if $opt !~ m/^serial\d+$/;
580 if ($param->{$opt} eq 'socket') {
581 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
583 die "only root can set '$opt' config for real devices\n";
590 my sub check_usb_perm
{
591 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
593 return 1 if $authuser eq 'root@pam';
595 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
597 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-usb', $value);
598 if ($device->{host
} && $device->{host
} !~ m/^spice$/i) {
599 die "only root can set '$opt' config for real devices\n";
600 } elsif ($device->{mapping
}) {
601 $rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
603 die "either 'host' or 'mapping' must be set.\n";
609 my sub check_vm_create_usb_perm
{
610 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
612 return 1 if $authuser eq 'root@pam';
614 foreach my $opt (keys %{$param}) {
615 next if $opt !~ m/^usb\d+$/;
616 check_usb_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
622 my sub check_hostpci_perm
{
623 my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
625 return 1 if $authuser eq 'root@pam';
627 my $device = PVE
::JSONSchema
::parse_property_string
('pve-qm-hostpci', $value);
628 if ($device->{host
}) {
629 die "only root can set '$opt' config for non-mapped devices\n";
630 } elsif ($device->{mapping
}) {
631 $rpcenv->check_full($authuser, "/mapping/pci/$device->{mapping}", ['Mapping.Use']);
632 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
634 die "either 'host' or 'mapping' must be set.\n";
640 my sub check_vm_create_hostpci_perm
{
641 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
643 return 1 if $authuser eq 'root@pam';
645 foreach my $opt (keys %{$param}) {
646 next if $opt !~ m/^hostpci\d+$/;
647 check_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
653 my $check_vm_modify_config_perm = sub {
654 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
656 return 1 if $authuser eq 'root@pam';
658 foreach my $opt (@$key_list) {
659 # some checks (e.g., disk, serial port, usb) need to be done somewhere
660 # else, as there the permission can be value dependend
661 next if PVE
::QemuServer
::is_valid_drivename
($opt);
662 next if $opt eq 'cdrom';
663 next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
664 next if $opt eq 'tags';
667 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
668 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
669 } elsif ($memoryoptions->{$opt}) {
670 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
671 } elsif ($hwtypeoptions->{$opt}) {
672 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
673 } elsif ($generaloptions->{$opt}) {
674 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
675 # special case for startup since it changes host behaviour
676 if ($opt eq 'startup') {
677 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
679 } elsif ($vmpoweroptions->{$opt}) {
680 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
681 } elsif ($diskoptions->{$opt}) {
682 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
683 } elsif ($opt =~ m/^net\d+$/) {
684 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
685 } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\d+$/) {
686 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
687 } elsif ($opt eq 'vmstate') {
688 # the user needs Disk and PowerMgmt privileges to change the vmstate
689 # also needs privileges on the storage, that will be checked later
690 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
692 # catches args, lock, etc.
693 # new options will be checked here
694 die "only root can set '$opt' config\n";
701 __PACKAGE__-
>register_method({
705 description
=> "Virtual machine index (per node).",
707 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
711 protected
=> 1, # qemu pid files are only readable by root
713 additionalProperties
=> 0,
715 node
=> get_standard_option
('pve-node'),
719 description
=> "Determine the full status of active VMs.",
727 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
729 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
734 my $rpcenv = PVE
::RPCEnvironment
::get
();
735 my $authuser = $rpcenv->get_user();
737 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
740 foreach my $vmid (keys %$vmstatus) {
741 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
743 my $data = $vmstatus->{$vmid};
750 my $parse_restore_archive = sub {
751 my ($storecfg, $archive) = @_;
753 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
757 if (defined($archive_storeid)) {
758 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
759 $res->{volid
} = $archive;
760 if ($scfg->{type
} eq 'pbs') {
761 $res->{type
} = 'pbs';
765 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
766 $res->{type
} = 'file';
767 $res->{path
} = $path;
772 __PACKAGE__-
>register_method({
776 description
=> "Create or restore a virtual machine.",
778 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
779 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
780 "If you create disks you need 'Datastore.AllocateSpace' on any used storage." .
781 "If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.",
782 user
=> 'all', # check inside
787 additionalProperties
=> 0,
788 properties
=> PVE
::QemuServer
::json_config_properties
(
790 node
=> get_standard_option
('pve-node'),
791 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
793 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
797 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
799 storage
=> get_standard_option
('pve-storage-id', {
800 description
=> "Default storage.",
802 completion
=> \
&PVE
::QemuServer
::complete_storage
,
807 description
=> "Allow to overwrite existing VM.",
808 requires
=> 'archive',
813 description
=> "Assign a unique random ethernet address.",
814 requires
=> 'archive',
819 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
820 requires
=> 'archive',
824 type
=> 'string', format
=> 'pve-poolid',
825 description
=> "Add the VM to the specified pool.",
828 description
=> "Override I/O bandwidth limit (in KiB/s).",
832 default => 'restore limit from datacenter or storage config',
838 description
=> "Start VM after it was created successfully.",
850 my $rpcenv = PVE
::RPCEnvironment
::get
();
851 my $authuser = $rpcenv->get_user();
853 my $node = extract_param
($param, 'node');
854 my $vmid = extract_param
($param, 'vmid');
856 my $archive = extract_param
($param, 'archive');
857 my $is_restore = !!$archive;
859 my $bwlimit = extract_param
($param, 'bwlimit');
860 my $force = extract_param
($param, 'force');
861 my $pool = extract_param
($param, 'pool');
862 my $start_after_create = extract_param
($param, 'start');
863 my $storage = extract_param
($param, 'storage');
864 my $unique = extract_param
($param, 'unique');
865 my $live_restore = extract_param
($param, 'live-restore');
867 if (defined(my $ssh_keys = $param->{sshkeys
})) {
868 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
869 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
872 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
873 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
875 PVE
::Cluster
::check_cfs_quorum
();
877 my $filename = PVE
::QemuConfig-
>config_file($vmid);
878 my $storecfg = PVE
::Storage
::config
();
880 if (defined($pool)) {
881 $rpcenv->check_pool_exist($pool);
884 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
885 if defined($storage);
887 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
889 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
891 } elsif ($archive && $force && (-f
$filename) &&
892 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
893 # OK: user has VM.Backup permissions and wants to restore an existing VM
899 for my $opt (sort keys $param->%*) {
900 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
901 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
905 if ($archive eq '-') {
906 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
907 $archive = { type
=> 'pipe' };
909 PVE
::Storage
::check_volume_access
(
918 $archive = $parse_restore_archive->($storecfg, $archive);
922 if (scalar(keys $param->%*) > 0) {
923 &$resolve_cdrom_alias($param);
925 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
927 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
929 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
930 check_vm_create_usb_perm
($rpcenv, $authuser, $vmid, $pool, $param);
931 check_vm_create_hostpci_perm
($rpcenv, $authuser, $vmid, $pool, $param);
933 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
934 &$check_cpu_model_access($rpcenv, $authuser, $param);
936 $check_drive_param->($param, $storecfg);
938 PVE
::QemuServer
::add_random_macs
($param);
941 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
943 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
944 die "$emsg $@" if $@;
946 my $restored_data = 0;
947 my $restorefn = sub {
948 my $conf = PVE
::QemuConfig-
>load_config($vmid);
950 PVE
::QemuConfig-
>check_protection($conf, $emsg);
952 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
955 my $restore_options = {
960 live
=> $live_restore,
961 override_conf
=> $param,
963 if (my $volid = $archive->{volid
}) {
964 # best effort, real check is after restoring!
966 my $old_conf = PVE
::Storage
::extract_vzdump_config
($storecfg, $volid);
967 PVE
::QemuServer
::restore_merge_config
("backup/qemu-server/$vmid.conf", $old_conf, $param);
970 warn "Could not extract backed up config: $@\n";
971 warn "Skipping early checks!\n";
973 PVE
::QemuServer
::check_restore_permissions
($rpcenv, $authuser, $merged);
976 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
977 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
979 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
980 } elsif ($archive->{type
} eq 'pbs') {
981 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
983 die "unknown backup archive type\n";
987 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
988 # Convert restored VM to template if backup was VM template
989 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
990 warn "Convert to template.\n";
991 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
996 # ensure no old replication state are exists
997 PVE
::ReplicationState
::delete_guest_states
($vmid);
999 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1001 if ($start_after_create && !$live_restore) {
1002 print "Execute autostart\n";
1003 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
1008 my $createfn = sub {
1009 # ensure no old replication state are exists
1010 PVE
::ReplicationState
::delete_guest_states
($vmid);
1014 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1016 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
1020 ($vollist, my $created_opts) = $create_disks->(
1031 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1033 if (!$conf->{boot
}) {
1034 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
1035 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
1038 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1039 PVE
::QemuServer
::assert_clipboard_config
($vga);
1041 # auto generate uuid if user did not specify smbios1 option
1042 if (!$conf->{smbios1
}) {
1043 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
1046 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
1047 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
1050 my $machine = $conf->{machine
};
1051 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
1052 # always pin Windows' machine version on create, they get to easily confused
1053 if (PVE
::QemuServer
::Helpers
::windows_version
($conf->{ostype
})) {
1054 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
1058 PVE
::QemuConfig-
>write_config($vmid, $conf);
1064 foreach my $volid (@$vollist) {
1065 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1071 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
1074 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1076 if ($start_after_create) {
1077 print "Execute autostart\n";
1078 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
1083 my ($code, $worker_name);
1085 $worker_name = 'qmrestore';
1087 eval { $restorefn->() };
1089 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
1091 if ($restored_data) {
1092 warn "error after data was restored, VM disks should be OK but config may "
1093 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1095 warn "error before or during data restore, some or all disks were not "
1096 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1102 $worker_name = 'qmcreate';
1104 eval { $createfn->() };
1107 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1108 unlink($conffile) or die "failed to remove config file: $!\n";
1116 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1119 __PACKAGE__-
>register_method({
1124 description
=> "Directory index",
1129 additionalProperties
=> 0,
1131 node
=> get_standard_option
('pve-node'),
1132 vmid
=> get_standard_option
('pve-vmid'),
1140 subdir
=> { type
=> 'string' },
1143 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1149 { subdir
=> 'config' },
1150 { subdir
=> 'cloudinit' },
1151 { subdir
=> 'pending' },
1152 { subdir
=> 'status' },
1153 { subdir
=> 'unlink' },
1154 { subdir
=> 'vncproxy' },
1155 { subdir
=> 'termproxy' },
1156 { subdir
=> 'migrate' },
1157 { subdir
=> 'resize' },
1158 { subdir
=> 'move' },
1159 { subdir
=> 'rrd' },
1160 { subdir
=> 'rrddata' },
1161 { subdir
=> 'monitor' },
1162 { subdir
=> 'agent' },
1163 { subdir
=> 'snapshot' },
1164 { subdir
=> 'spiceproxy' },
1165 { subdir
=> 'sendkey' },
1166 { subdir
=> 'firewall' },
1167 { subdir
=> 'mtunnel' },
1168 { subdir
=> 'remote_migrate' },
1174 __PACKAGE__-
>register_method ({
1175 subclass
=> "PVE::API2::Firewall::VM",
1176 path
=> '{vmid}/firewall',
1179 __PACKAGE__-
>register_method ({
1180 subclass
=> "PVE::API2::Qemu::Agent",
1181 path
=> '{vmid}/agent',
1184 __PACKAGE__-
>register_method({
1186 path
=> '{vmid}/rrd',
1188 protected
=> 1, # fixme: can we avoid that?
1190 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1192 description
=> "Read VM RRD statistics (returns PNG)",
1194 additionalProperties
=> 0,
1196 node
=> get_standard_option
('pve-node'),
1197 vmid
=> get_standard_option
('pve-vmid'),
1199 description
=> "Specify the time frame you are interested in.",
1201 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1204 description
=> "The list of datasources you want to display.",
1205 type
=> 'string', format
=> 'pve-configid-list',
1208 description
=> "The RRD consolidation function",
1210 enum
=> [ 'AVERAGE', 'MAX' ],
1218 filename
=> { type
=> 'string' },
1224 return PVE
::RRD
::create_rrd_graph
(
1225 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1226 $param->{ds
}, $param->{cf
});
1230 __PACKAGE__-
>register_method({
1232 path
=> '{vmid}/rrddata',
1234 protected
=> 1, # fixme: can we avoid that?
1236 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1238 description
=> "Read VM RRD statistics",
1240 additionalProperties
=> 0,
1242 node
=> get_standard_option
('pve-node'),
1243 vmid
=> get_standard_option
('pve-vmid'),
1245 description
=> "Specify the time frame you are interested in.",
1247 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1250 description
=> "The RRD consolidation function",
1252 enum
=> [ 'AVERAGE', 'MAX' ],
1267 return PVE
::RRD
::create_rrd_data
(
1268 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1272 __PACKAGE__-
>register_method({
1273 name
=> 'vm_config',
1274 path
=> '{vmid}/config',
1277 description
=> "Get the virtual machine configuration with pending configuration " .
1278 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1280 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1283 additionalProperties
=> 0,
1285 node
=> get_standard_option
('pve-node'),
1286 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1288 description
=> "Get current values (instead of pending values).",
1293 snapshot
=> get_standard_option
('pve-snapshot-name', {
1294 description
=> "Fetch config values from given snapshot.",
1297 my ($cmd, $pname, $cur, $args) = @_;
1298 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1304 description
=> "The VM configuration.",
1306 properties
=> PVE
::QemuServer
::json_config_properties
({
1309 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1316 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1317 current
=> "cannot use 'snapshot' parameter with 'current'"})
1318 if ($param->{snapshot
} && $param->{current
});
1321 if ($param->{snapshot
}) {
1322 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1324 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1326 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1331 __PACKAGE__-
>register_method({
1332 name
=> 'vm_pending',
1333 path
=> '{vmid}/pending',
1336 description
=> "Get the virtual machine configuration with both current and pending values.",
1338 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1341 additionalProperties
=> 0,
1343 node
=> get_standard_option
('pve-node'),
1344 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1353 description
=> "Configuration option name.",
1357 description
=> "Current value.",
1362 description
=> "Pending value.",
1367 description
=> "Indicates a pending delete request if present and not 0. " .
1368 "The value 2 indicates a force-delete request.",
1380 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1382 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1384 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1385 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1387 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1390 __PACKAGE__-
>register_method({
1391 name
=> 'cloudinit_pending',
1392 path
=> '{vmid}/cloudinit',
1395 description
=> "Get the cloudinit configuration with both current and pending values.",
1397 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1400 additionalProperties
=> 0,
1402 node
=> get_standard_option
('pve-node'),
1403 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1412 description
=> "Configuration option name.",
1416 description
=> "Value as it was used to generate the current cloudinit image.",
1421 description
=> "The new pending value.",
1426 description
=> "Indicates a pending delete request if present and not 0. ",
1438 my $vmid = $param->{vmid
};
1439 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1441 my $ci = $conf->{cloudinit
};
1443 $conf->{cipassword
} = '**********' if exists $conf->{cipassword
};
1444 $ci->{cipassword
} = '**********' if exists $ci->{cipassword
};
1448 # All the values that got added
1449 my $added = delete($ci->{added
}) // '';
1450 for my $key (PVE
::Tools
::split_list
($added)) {
1451 push @$res, { key
=> $key, pending
=> $conf->{$key} };
1454 # All already existing values (+ their new value, if it exists)
1455 for my $opt (keys %$cloudinitoptions) {
1456 next if !$conf->{$opt};
1457 next if $added =~ m/$opt/;
1462 if (my $pending = $ci->{$opt}) {
1463 $item->{value
} = $pending;
1464 $item->{pending
} = $conf->{$opt};
1466 $item->{value
} = $conf->{$opt},
1472 # Now, we'll find the deleted ones
1473 for my $opt (keys %$ci) {
1474 next if $conf->{$opt};
1475 push @$res, { key
=> $opt, delete => 1 };
1481 __PACKAGE__-
>register_method({
1482 name
=> 'cloudinit_update',
1483 path
=> '{vmid}/cloudinit',
1487 description
=> "Regenerate and change cloudinit config drive.",
1489 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Cloudinit']],
1492 additionalProperties
=> 0,
1494 node
=> get_standard_option
('pve-node'),
1495 vmid
=> get_standard_option
('pve-vmid'),
1498 returns
=> { type
=> 'null' },
1502 my $rpcenv = PVE
::RPCEnvironment
::get
();
1503 my $authuser = $rpcenv->get_user();
1505 my $vmid = extract_param
($param, 'vmid');
1507 PVE
::QemuConfig-
>lock_config($vmid, sub {
1508 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1509 PVE
::QemuConfig-
>check_lock($conf);
1511 my $storecfg = PVE
::Storage
::config
();
1512 PVE
::QemuServer
::vmconfig_update_cloudinit_drive
($storecfg, $conf, $vmid);
1517 # POST/PUT {vmid}/config implementation
1519 # The original API used PUT (idempotent) an we assumed that all operations
1520 # are fast. But it turned out that almost any configuration change can
1521 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1522 # time to complete and have side effects (not idempotent).
1524 # The new implementation uses POST and forks a worker process. We added
1525 # a new option 'background_delay'. If specified we wait up to
1526 # 'background_delay' second for the worker task to complete. It returns null
1527 # if the task is finished within that time, else we return the UPID.
1529 my $update_vm_api = sub {
1530 my ($param, $sync) = @_;
1532 my $rpcenv = PVE
::RPCEnvironment
::get
();
1534 my $authuser = $rpcenv->get_user();
1536 my $node = extract_param
($param, 'node');
1538 my $vmid = extract_param
($param, 'vmid');
1540 my $digest = extract_param
($param, 'digest');
1542 my $background_delay = extract_param
($param, 'background_delay');
1544 my $skip_cloud_init = extract_param
($param, 'skip_cloud_init');
1546 if (defined(my $cipassword = $param->{cipassword
})) {
1547 # Same logic as in cloud-init (but with the regex fixed...)
1548 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1549 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1552 my @paramarr = (); # used for log message
1553 foreach my $key (sort keys %$param) {
1554 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1555 push @paramarr, "-$key", $value;
1558 my $skiplock = extract_param
($param, 'skiplock');
1559 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1560 if $skiplock && $authuser ne 'root@pam';
1562 my $delete_str = extract_param
($param, 'delete');
1564 my $revert_str = extract_param
($param, 'revert');
1566 my $force = extract_param
($param, 'force');
1568 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1569 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1570 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1573 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
1574 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1576 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1578 my $storecfg = PVE
::Storage
::config
();
1580 &$resolve_cdrom_alias($param);
1582 # now try to verify all parameters
1585 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1586 if (!PVE
::QemuServer
::option_exists
($opt)) {
1587 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1590 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1591 "-revert $opt' at the same time" })
1592 if defined($param->{$opt});
1594 $revert->{$opt} = 1;
1598 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1599 $opt = 'ide2' if $opt eq 'cdrom';
1601 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1602 "-delete $opt' at the same time" })
1603 if defined($param->{$opt});
1605 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1606 "-revert $opt' at the same time" })
1609 if (!PVE
::QemuServer
::option_exists
($opt)) {
1610 raise_param_exc
({ delete => "unknown option '$opt'" });
1616 my $repl_conf = PVE
::ReplicationConfig-
>new();
1617 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1618 my $check_replication = sub {
1620 return if !$is_replicated;
1621 my $volid = $drive->{file
};
1622 return if !$volid || !($drive->{replicate
}//1);
1623 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1625 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1626 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1627 if !defined($storeid);
1629 return if defined($volname) && $volname eq 'cloudinit';
1632 if ($volid =~ $NEW_DISK_RE) {
1634 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1636 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1638 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1639 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1640 return if $scfg->{shared
};
1641 die "cannot add non-replicatable volume to a replicated VM\n";
1644 $check_drive_param->($param, $storecfg, $check_replication);
1646 foreach my $opt (keys %$param) {
1647 if ($opt =~ m/^net(\d+)$/) {
1649 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1650 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1651 } elsif ($opt eq 'vmgenid') {
1652 if ($param->{$opt} eq '1') {
1653 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1655 } elsif ($opt eq 'hookscript') {
1656 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1657 raise_param_exc
({ $opt => $@ }) if $@;
1661 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1663 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1665 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1667 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
1669 my $updatefn = sub {
1671 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1673 die "checksum missmatch (file change by other user?)\n"
1674 if $digest && $digest ne $conf->{digest
};
1676 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1678 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1679 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1680 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1681 delete $conf->{lock}; # for check lock check, not written out
1682 push @delete, 'lock'; # this is the real deal to write it out
1684 push @delete, 'runningmachine' if $conf->{runningmachine
};
1685 push @delete, 'runningcpu' if $conf->{runningcpu
};
1688 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1690 foreach my $opt (keys %$revert) {
1691 if (defined($conf->{$opt})) {
1692 $param->{$opt} = $conf->{$opt};
1693 } elsif (defined($conf->{pending
}->{$opt})) {
1698 if ($param->{memory
} || defined($param->{balloon
})) {
1700 my $memory = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
};
1701 my $maxmem = get_current_memory
($memory);
1702 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1704 die "balloon value too large (must be smaller than assigned memory)\n"
1705 if $balloon && $balloon > $maxmem;
1708 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1712 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1714 # write updates to pending section
1716 my $modified = {}; # record what $option we modify
1719 if (my $boot = $conf->{boot
}) {
1720 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1721 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1723 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1725 my $check_drive_perms = sub {
1726 my ($opt, $val) = @_;
1727 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1728 if (PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
1729 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Cloudinit', 'VM.Config.CDROM']);
1730 } elsif (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) { # CDROM
1731 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1733 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1738 foreach my $opt (@delete) {
1739 $modified->{$opt} = 1;
1740 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1742 # value of what we want to delete, independent if pending or not
1743 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1744 if (!defined($val)) {
1745 warn "cannot delete '$opt' - not set in current configuration!\n";
1746 $modified->{$opt} = 0;
1749 my $is_pending_val = defined($conf->{pending
}->{$opt});
1750 delete $conf->{pending
}->{$opt};
1752 # remove from bootorder if necessary
1753 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1754 @bootorder = grep {$_ ne $opt} @bootorder;
1755 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1756 $modified->{boot
} = 1;
1759 if ($opt =~ m/^unused/) {
1760 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1761 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1762 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1763 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1764 delete $conf->{$opt};
1765 PVE
::QemuConfig-
>write_config($vmid, $conf);
1767 } elsif ($opt eq 'vmstate') {
1768 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1769 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1770 delete $conf->{$opt};
1771 PVE
::QemuConfig-
>write_config($vmid, $conf);
1773 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1774 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1775 $check_drive_perms->($opt, $val);
1776 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1778 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1779 PVE
::QemuConfig-
>write_config($vmid, $conf);
1780 } elsif ($opt =~ m/^serial\d+$/) {
1781 if ($val eq 'socket') {
1782 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1783 } elsif ($authuser ne 'root@pam') {
1784 die "only root can delete '$opt' config for real devices\n";
1786 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1787 PVE
::QemuConfig-
>write_config($vmid, $conf);
1788 } elsif ($opt =~ m/^usb\d+$/) {
1789 check_usb_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 =~ m/^hostpci\d+$/) {
1793 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $val);
1794 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1795 PVE
::QemuConfig-
>write_config($vmid, $conf);
1796 } elsif ($opt eq 'tags') {
1797 assert_tag_permissions
($vmid, $val, '', $rpcenv, $authuser);
1798 delete $conf->{$opt};
1799 PVE
::QemuConfig-
>write_config($vmid, $conf);
1800 } elsif ($opt =~ m/^net\d+$/) {
1801 if ($conf->{$opt}) {
1802 PVE
::QemuServer
::check_bridge_access
(
1805 { $opt => $conf->{$opt} },
1808 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1809 PVE
::QemuConfig-
>write_config($vmid, $conf);
1811 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1812 PVE
::QemuConfig-
>write_config($vmid, $conf);
1816 foreach my $opt (keys %$param) { # add/change
1817 $modified->{$opt} = 1;
1818 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1819 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1821 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1823 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1825 if ($conf->{$opt}) {
1826 $check_drive_perms->($opt, $conf->{$opt});
1830 $check_drive_perms->($opt, $param->{$opt});
1831 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1832 if defined($conf->{pending
}->{$opt});
1834 my (undef, $created_opts) = $create_disks->(
1842 {$opt => $param->{$opt}},
1844 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1846 # default legacy boot order implies all cdroms anyway
1848 # append new CD drives to bootorder to mark them bootable
1849 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1850 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1851 push @bootorder, $opt;
1852 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1853 $modified->{boot
} = 1;
1856 } elsif ($opt =~ m/^serial\d+/) {
1857 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1858 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1859 } elsif ($authuser ne 'root@pam') {
1860 die "only root can modify '$opt' config for real devices\n";
1862 $conf->{pending
}->{$opt} = $param->{$opt};
1863 } elsif ($opt eq 'vga') {
1864 my $vga = PVE
::QemuServer
::parse_vga
($param->{$opt});
1865 PVE
::QemuServer
::assert_clipboard_config
($vga);
1866 $conf->{pending
}->{$opt} = $param->{$opt};
1867 } elsif ($opt =~ m/^usb\d+/) {
1868 if (my $olddevice = $conf->{$opt}) {
1869 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});
1871 check_usb_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1872 $conf->{pending
}->{$opt} = $param->{$opt};
1873 } elsif ($opt =~ m/^hostpci\d+$/) {
1874 if (my $oldvalue = $conf->{$opt}) {
1875 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);
1877 check_hostpci_perm
($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
1878 $conf->{pending
}->{$opt} = $param->{$opt};
1879 } elsif ($opt eq 'tags') {
1880 assert_tag_permissions
($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
1881 $conf->{pending
}->{$opt} = PVE
::GuestHelpers
::get_unique_tags
($param->{$opt});
1882 } elsif ($opt =~ m/^net\d+$/) {
1883 if ($conf->{$opt}) {
1884 PVE
::QemuServer
::check_bridge_access
(
1887 { $opt => $conf->{$opt} },
1890 $conf->{pending
}->{$opt} = $param->{$opt};
1892 $conf->{pending
}->{$opt} = $param->{$opt};
1894 if ($opt eq 'boot') {
1895 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1896 if ($new_bootcfg->{order
}) {
1897 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1898 for my $dev (@devs) {
1899 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1900 my $deleted = grep {$_ eq $dev} @delete;
1901 die "invalid bootorder: device '$dev' does not exist'\n"
1902 if !$exists || $deleted;
1905 # remove legacy boot order settings if new one set
1906 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1907 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1908 if $conf->{bootdisk
};
1912 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1913 PVE
::QemuConfig-
>write_config($vmid, $conf);
1916 # remove pending changes when nothing changed
1917 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1918 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1919 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1921 return if !scalar(keys %{$conf->{pending
}});
1923 my $running = PVE
::QemuServer
::check_running
($vmid);
1925 # apply pending changes
1927 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1931 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1933 # cloud_init must be skipped if we are in an incoming, remote live migration
1934 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors, $skip_cloud_init);
1936 raise_param_exc
($errors) if scalar(keys %$errors);
1945 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1947 if ($background_delay) {
1949 # Note: It would be better to do that in the Event based HTTPServer
1950 # to avoid blocking call to sleep.
1952 my $end_time = time() + $background_delay;
1954 my $task = PVE
::Tools
::upid_decode
($upid);
1957 while (time() < $end_time) {
1958 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1960 sleep(1); # this gets interrupted when child process ends
1964 my $status = PVE
::Tools
::upid_read_status
($upid);
1965 return if !PVE
::Tools
::upid_status_is_error
($status);
1966 die "failed to update VM $vmid: $status\n";
1974 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1977 my $vm_config_perm_list = [
1982 'VM.Config.Network',
1984 'VM.Config.Options',
1985 'VM.Config.Cloudinit',
1988 __PACKAGE__-
>register_method({
1989 name
=> 'update_vm_async',
1990 path
=> '{vmid}/config',
1994 description
=> "Set virtual machine options (asynchrounous API).",
1996 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1999 additionalProperties
=> 0,
2000 properties
=> PVE
::QemuServer
::json_config_properties
(
2002 node
=> get_standard_option
('pve-node'),
2003 vmid
=> get_standard_option
('pve-vmid'),
2004 skiplock
=> get_standard_option
('skiplock'),
2006 type
=> 'string', format
=> 'pve-configid-list',
2007 description
=> "A list of settings you want to delete.",
2011 type
=> 'string', format
=> 'pve-configid-list',
2012 description
=> "Revert a pending change.",
2017 description
=> $opt_force_description,
2019 requires
=> 'delete',
2023 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2027 background_delay
=> {
2029 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
2035 1, # with_disk_alloc
2042 code
=> $update_vm_api,
2045 __PACKAGE__-
>register_method({
2046 name
=> 'update_vm',
2047 path
=> '{vmid}/config',
2051 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
2053 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
2056 additionalProperties
=> 0,
2057 properties
=> PVE
::QemuServer
::json_config_properties
(
2059 node
=> get_standard_option
('pve-node'),
2060 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2061 skiplock
=> get_standard_option
('skiplock'),
2063 type
=> 'string', format
=> 'pve-configid-list',
2064 description
=> "A list of settings you want to delete.",
2068 type
=> 'string', format
=> 'pve-configid-list',
2069 description
=> "Revert a pending change.",
2074 description
=> $opt_force_description,
2076 requires
=> 'delete',
2080 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2085 1, # with_disk_alloc
2088 returns
=> { type
=> 'null' },
2091 &$update_vm_api($param, 1);
2096 __PACKAGE__-
>register_method({
2097 name
=> 'destroy_vm',
2102 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
2103 ." and firewall rules",
2105 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2108 additionalProperties
=> 0,
2110 node
=> get_standard_option
('pve-node'),
2111 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2112 skiplock
=> get_standard_option
('skiplock'),
2115 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
2118 'destroy-unreferenced-disks' => {
2120 description
=> "If set, destroy additionally all disks not referenced in the config"
2121 ." but with a matching VMID from all enabled storages.",
2133 my $rpcenv = PVE
::RPCEnvironment
::get
();
2134 my $authuser = $rpcenv->get_user();
2135 my $vmid = $param->{vmid
};
2137 my $skiplock = $param->{skiplock
};
2138 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2139 if $skiplock && $authuser ne 'root@pam';
2141 my $early_checks = sub {
2143 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2144 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
2146 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
2148 if (!$param->{purge
}) {
2149 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
2151 # don't allow destroy if with replication jobs but no purge param
2152 my $repl_conf = PVE
::ReplicationConfig-
>new();
2153 $repl_conf->check_for_existing_jobs($vmid);
2156 die "VM $vmid is running - destroy failed\n"
2157 if PVE
::QemuServer
::check_running
($vmid);
2167 my $storecfg = PVE
::Storage
::config
();
2169 syslog
('info', "destroy VM $vmid: $upid\n");
2170 PVE
::QemuConfig-
>lock_config($vmid, sub {
2171 # repeat, config might have changed
2172 my $ha_managed = $early_checks->();
2174 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
2176 PVE
::QemuServer
::destroy_vm
(
2179 $skiplock, { lock => 'destroyed' },
2180 $purge_unreferenced,
2183 PVE
::AccessControl
::remove_vm_access
($vmid);
2184 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2185 if ($param->{purge
}) {
2186 print "purging VM $vmid from related configurations..\n";
2187 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2188 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2191 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2192 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2196 # only now remove the zombie config, else we can have reuse race
2197 PVE
::QemuConfig-
>destroy_config($vmid);
2201 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2204 __PACKAGE__-
>register_method({
2206 path
=> '{vmid}/unlink',
2210 description
=> "Unlink/delete disk images.",
2212 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2215 additionalProperties
=> 0,
2217 node
=> get_standard_option
('pve-node'),
2218 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2220 type
=> 'string', format
=> 'pve-configid-list',
2221 description
=> "A list of disk IDs you want to delete.",
2225 description
=> $opt_force_description,
2230 returns
=> { type
=> 'null'},
2234 $param->{delete} = extract_param
($param, 'idlist');
2236 __PACKAGE__-
>update_vm($param);
2241 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2242 my $gen_rand_chars = sub {
2245 die "invalid length $length" if $length < 1;
2247 my $min = ord('!'); # first printable ascii
2249 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2250 die "failed to generate random bytes!\n"
2253 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2260 __PACKAGE__-
>register_method({
2262 path
=> '{vmid}/vncproxy',
2266 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2268 description
=> "Creates a TCP VNC proxy connections.",
2270 additionalProperties
=> 0,
2272 node
=> get_standard_option
('pve-node'),
2273 vmid
=> get_standard_option
('pve-vmid'),
2277 description
=> "Prepare for websocket upgrade (only required when using "
2278 ."serial terminal, otherwise upgrade is always possible).",
2280 'generate-password' => {
2284 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2289 additionalProperties
=> 0,
2291 user
=> { type
=> 'string' },
2292 ticket
=> { type
=> 'string' },
2295 description
=> "Returned if requested with 'generate-password' param."
2296 ." Consists of printable ASCII characters ('!' .. '~').",
2299 cert
=> { type
=> 'string' },
2300 port
=> { type
=> 'integer' },
2301 upid
=> { type
=> 'string' },
2307 my $rpcenv = PVE
::RPCEnvironment
::get
();
2309 my $authuser = $rpcenv->get_user();
2311 my $vmid = $param->{vmid
};
2312 my $node = $param->{node
};
2313 my $websocket = $param->{websocket
};
2315 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2319 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2320 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2323 my $authpath = "/vms/$vmid";
2325 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2326 my $password = $ticket;
2327 if ($param->{'generate-password'}) {
2328 $password = $gen_rand_chars->(8);
2331 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2337 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2338 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2339 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2340 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2341 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2343 $family = PVE
::Tools
::get_host_address_family
($node);
2346 my $port = PVE
::Tools
::next_vnc_port
($family);
2353 syslog
('info', "starting vnc proxy $upid\n");
2357 if (defined($serial)) {
2359 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2361 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2362 '-timeout', $timeout, '-authpath', $authpath,
2363 '-perm', 'Sys.Console'];
2365 if ($param->{websocket
}) {
2366 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2367 push @$cmd, '-notls', '-listen', 'localhost';
2370 push @$cmd, '-c', @$remcmd, @$termcmd;
2372 PVE
::Tools
::run_command
($cmd);
2376 $ENV{LC_PVE_TICKET
} = $password; # set ticket with "qm vncproxy"
2378 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2380 my $sock = IO
::Socket
::IP-
>new(
2385 GetAddrInfoFlags
=> 0,
2386 ) or die "failed to create socket: $!\n";
2387 # Inside the worker we shouldn't have any previous alarms
2388 # running anyway...:
2390 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2392 accept(my $cli, $sock) or die "connection failed: $!\n";
2395 if (PVE
::Tools
::run_command
($cmd,
2396 output
=> '>&'.fileno($cli),
2397 input
=> '<&'.fileno($cli),
2400 die "Failed to run vncproxy.\n";
2407 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2409 PVE
::Tools
::wait_for_vnc_port
($port);
2418 $res->{password
} = $password if $param->{'generate-password'};
2423 __PACKAGE__-
>register_method({
2424 name
=> 'termproxy',
2425 path
=> '{vmid}/termproxy',
2429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2431 description
=> "Creates a TCP proxy connections.",
2433 additionalProperties
=> 0,
2435 node
=> get_standard_option
('pve-node'),
2436 vmid
=> get_standard_option
('pve-vmid'),
2440 enum
=> [qw(serial0 serial1 serial2 serial3)],
2441 description
=> "opens a serial terminal (defaults to display)",
2446 additionalProperties
=> 0,
2448 user
=> { type
=> 'string' },
2449 ticket
=> { type
=> 'string' },
2450 port
=> { type
=> 'integer' },
2451 upid
=> { type
=> 'string' },
2457 my $rpcenv = PVE
::RPCEnvironment
::get
();
2459 my $authuser = $rpcenv->get_user();
2461 my $vmid = $param->{vmid
};
2462 my $node = $param->{node
};
2463 my $serial = $param->{serial
};
2465 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2467 if (!defined($serial)) {
2469 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2470 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2474 my $authpath = "/vms/$vmid";
2476 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2481 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2482 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2483 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2484 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2485 push @$remcmd, '--';
2487 $family = PVE
::Tools
::get_host_address_family
($node);
2490 my $port = PVE
::Tools
::next_vnc_port
($family);
2492 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2493 push @$termcmd, '-iface', $serial if $serial;
2498 syslog
('info', "starting qemu termproxy $upid\n");
2500 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2501 '--perm', 'VM.Console', '--'];
2502 push @$cmd, @$remcmd, @$termcmd;
2504 PVE
::Tools
::run_command
($cmd);
2507 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2509 PVE
::Tools
::wait_for_vnc_port
($port);
2519 __PACKAGE__-
>register_method({
2520 name
=> 'vncwebsocket',
2521 path
=> '{vmid}/vncwebsocket',
2524 description
=> "You also need to pass a valid ticket (vncticket).",
2525 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2527 description
=> "Opens a weksocket for VNC traffic.",
2529 additionalProperties
=> 0,
2531 node
=> get_standard_option
('pve-node'),
2532 vmid
=> get_standard_option
('pve-vmid'),
2534 description
=> "Ticket from previous call to vncproxy.",
2539 description
=> "Port number returned by previous vncproxy call.",
2549 port
=> { type
=> 'string' },
2555 my $rpcenv = PVE
::RPCEnvironment
::get
();
2557 my $authuser = $rpcenv->get_user();
2559 my $vmid = $param->{vmid
};
2560 my $node = $param->{node
};
2562 my $authpath = "/vms/$vmid";
2564 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2566 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2568 # Note: VNC ports are acessible from outside, so we do not gain any
2569 # security if we verify that $param->{port} belongs to VM $vmid. This
2570 # check is done by verifying the VNC ticket (inside VNC protocol).
2572 my $port = $param->{port
};
2574 return { port
=> $port };
2577 __PACKAGE__-
>register_method({
2578 name
=> 'spiceproxy',
2579 path
=> '{vmid}/spiceproxy',
2584 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2586 description
=> "Returns a SPICE configuration to connect to the VM.",
2588 additionalProperties
=> 0,
2590 node
=> get_standard_option
('pve-node'),
2591 vmid
=> get_standard_option
('pve-vmid'),
2592 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2595 returns
=> get_standard_option
('remote-viewer-config'),
2599 my $rpcenv = PVE
::RPCEnvironment
::get
();
2601 my $authuser = $rpcenv->get_user();
2603 my $vmid = $param->{vmid
};
2604 my $node = $param->{node
};
2605 my $proxy = $param->{proxy
};
2607 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2608 my $title = "VM $vmid";
2609 $title .= " - ". $conf->{name
} if $conf->{name
};
2611 my $port = PVE
::QemuServer
::spice_port
($vmid);
2613 my ($ticket, undef, $remote_viewer_config) =
2614 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2616 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2617 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2619 return $remote_viewer_config;
2622 __PACKAGE__-
>register_method({
2624 path
=> '{vmid}/status',
2627 description
=> "Directory index",
2632 additionalProperties
=> 0,
2634 node
=> get_standard_option
('pve-node'),
2635 vmid
=> get_standard_option
('pve-vmid'),
2643 subdir
=> { type
=> 'string' },
2646 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2652 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2655 { subdir
=> 'current' },
2656 { subdir
=> 'start' },
2657 { subdir
=> 'stop' },
2658 { subdir
=> 'reset' },
2659 { subdir
=> 'shutdown' },
2660 { subdir
=> 'suspend' },
2661 { subdir
=> 'reboot' },
2667 __PACKAGE__-
>register_method({
2668 name
=> 'vm_status',
2669 path
=> '{vmid}/status/current',
2672 protected
=> 1, # qemu pid files are only readable by root
2673 description
=> "Get virtual machine status.",
2675 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2678 additionalProperties
=> 0,
2680 node
=> get_standard_option
('pve-node'),
2681 vmid
=> get_standard_option
('pve-vmid'),
2687 %$PVE::QemuServer
::vmstatus_return_properties
,
2689 description
=> "HA manager service status.",
2693 description
=> "QEMU VGA configuration supports spice.",
2698 description
=> "QEMU Guest Agent is enabled in config.",
2708 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2710 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2711 my $status = $vmstatus->{$param->{vmid
}};
2713 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2716 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2717 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2718 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2719 $status->{spice
} = 1 if $spice;
2721 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2726 __PACKAGE__-
>register_method({
2728 path
=> '{vmid}/status/start',
2732 description
=> "Start virtual machine.",
2734 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2737 additionalProperties
=> 0,
2739 node
=> get_standard_option
('pve-node'),
2740 vmid
=> get_standard_option
('pve-vmid',
2741 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2742 skiplock
=> get_standard_option
('skiplock'),
2743 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2744 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2747 enum
=> ['secure', 'insecure'],
2748 description
=> "Migration traffic is encrypted using an SSH " .
2749 "tunnel by default. On secure, completely private networks " .
2750 "this can be disabled to increase performance.",
2753 migration_network
=> {
2754 type
=> 'string', format
=> 'CIDR',
2755 description
=> "CIDR of the (sub) network that is used for migration.",
2758 machine
=> get_standard_option
('pve-qemu-machine'),
2760 description
=> "Override QEMU's -cpu argument with the given string.",
2764 targetstorage
=> get_standard_option
('pve-targetstorage'),
2766 description
=> "Wait maximal timeout seconds.",
2769 default => 'max(30, vm memory in GiB)',
2780 my $rpcenv = PVE
::RPCEnvironment
::get
();
2781 my $authuser = $rpcenv->get_user();
2783 my $node = extract_param
($param, 'node');
2784 my $vmid = extract_param
($param, 'vmid');
2785 my $timeout = extract_param
($param, 'timeout');
2786 my $machine = extract_param
($param, 'machine');
2788 my $get_root_param = sub {
2789 my $value = extract_param
($param, $_[0]);
2790 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2791 if $value && $authuser ne 'root@pam';
2795 my $stateuri = $get_root_param->('stateuri');
2796 my $skiplock = $get_root_param->('skiplock');
2797 my $migratedfrom = $get_root_param->('migratedfrom');
2798 my $migration_type = $get_root_param->('migration_type');
2799 my $migration_network = $get_root_param->('migration_network');
2800 my $targetstorage = $get_root_param->('targetstorage');
2801 my $force_cpu = $get_root_param->('force-cpu');
2805 if ($targetstorage) {
2806 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2808 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2809 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2813 # read spice ticket from STDIN
2815 my $nbd_protocol_version = 0;
2816 my $replicated_volumes = {};
2817 my $offline_volumes = {};
2818 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2819 while (defined(my $line = <STDIN
>)) {
2821 if ($line =~ m/^spice_ticket: (.+)$/) {
2823 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2824 $nbd_protocol_version = $1;
2825 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2826 $replicated_volumes->{$1} = 1;
2827 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2828 $offline_volumes->{tpmstate0
} = $1;
2829 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2830 $offline_volumes->{$1} = $2;
2831 } elsif (!$spice_ticket) {
2832 # fallback for old source node
2833 $spice_ticket = $line;
2835 warn "unknown 'start' parameter on STDIN: '$line'\n";
2840 PVE
::Cluster
::check_cfs_quorum
();
2842 my $storecfg = PVE
::Storage
::config
();
2844 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2848 print "Requesting HA start for VM $vmid\n";
2850 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2851 PVE
::Tools
::run_command
($cmd);
2855 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2862 syslog
('info', "start VM $vmid: $upid\n");
2864 my $migrate_opts = {
2865 migratedfrom
=> $migratedfrom,
2866 spice_ticket
=> $spice_ticket,
2867 network
=> $migration_network,
2868 type
=> $migration_type,
2869 storagemap
=> $storagemap,
2870 nbd_proto_version
=> $nbd_protocol_version,
2871 replicated_volumes
=> $replicated_volumes,
2872 offline_volumes
=> $offline_volumes,
2876 statefile
=> $stateuri,
2877 skiplock
=> $skiplock,
2878 forcemachine
=> $machine,
2879 timeout
=> $timeout,
2880 forcecpu
=> $force_cpu,
2883 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2887 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2891 __PACKAGE__-
>register_method({
2893 path
=> '{vmid}/status/stop',
2897 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2898 "is akin to pulling the power plug of a running computer and may damage the VM data",
2900 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2903 additionalProperties
=> 0,
2905 node
=> get_standard_option
('pve-node'),
2906 vmid
=> get_standard_option
('pve-vmid',
2907 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2908 skiplock
=> get_standard_option
('skiplock'),
2909 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2911 description
=> "Wait maximal timeout seconds.",
2917 description
=> "Do not deactivate storage volumes.",
2930 my $rpcenv = PVE
::RPCEnvironment
::get
();
2931 my $authuser = $rpcenv->get_user();
2933 my $node = extract_param
($param, 'node');
2934 my $vmid = extract_param
($param, 'vmid');
2936 my $skiplock = extract_param
($param, 'skiplock');
2937 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2938 if $skiplock && $authuser ne 'root@pam';
2940 my $keepActive = extract_param
($param, 'keepActive');
2941 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2942 if $keepActive && $authuser ne 'root@pam';
2944 my $migratedfrom = extract_param
($param, 'migratedfrom');
2945 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2946 if $migratedfrom && $authuser ne 'root@pam';
2949 my $storecfg = PVE
::Storage
::config
();
2951 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2956 print "Requesting HA stop for VM $vmid\n";
2958 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2959 PVE
::Tools
::run_command
($cmd);
2963 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2969 syslog
('info', "stop VM $vmid: $upid\n");
2971 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2972 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2976 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2980 __PACKAGE__-
>register_method({
2982 path
=> '{vmid}/status/reset',
2986 description
=> "Reset virtual machine.",
2988 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2991 additionalProperties
=> 0,
2993 node
=> get_standard_option
('pve-node'),
2994 vmid
=> get_standard_option
('pve-vmid',
2995 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2996 skiplock
=> get_standard_option
('skiplock'),
3005 my $rpcenv = PVE
::RPCEnvironment
::get
();
3007 my $authuser = $rpcenv->get_user();
3009 my $node = extract_param
($param, 'node');
3011 my $vmid = extract_param
($param, 'vmid');
3013 my $skiplock = extract_param
($param, 'skiplock');
3014 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3015 if $skiplock && $authuser ne 'root@pam';
3017 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3022 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
3027 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
3030 __PACKAGE__-
>register_method({
3031 name
=> 'vm_shutdown',
3032 path
=> '{vmid}/status/shutdown',
3036 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
3037 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
3039 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3042 additionalProperties
=> 0,
3044 node
=> get_standard_option
('pve-node'),
3045 vmid
=> get_standard_option
('pve-vmid',
3046 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3047 skiplock
=> get_standard_option
('skiplock'),
3049 description
=> "Wait maximal timeout seconds.",
3055 description
=> "Make sure the VM stops.",
3061 description
=> "Do not deactivate storage volumes.",
3074 my $rpcenv = PVE
::RPCEnvironment
::get
();
3075 my $authuser = $rpcenv->get_user();
3077 my $node = extract_param
($param, 'node');
3078 my $vmid = extract_param
($param, 'vmid');
3080 my $skiplock = extract_param
($param, 'skiplock');
3081 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3082 if $skiplock && $authuser ne 'root@pam';
3084 my $keepActive = extract_param
($param, 'keepActive');
3085 raise_param_exc
({ keepActive
=> "Only root may use this option." })
3086 if $keepActive && $authuser ne 'root@pam';
3088 my $storecfg = PVE
::Storage
::config
();
3092 # sending a graceful shutdown command to paused VMs runs into timeouts, and even worse, when
3093 # the VM gets resumed later, it still gets the request delivered and powers off
3094 if (PVE
::QemuServer
::vm_is_paused
($vmid, 1)) {
3095 if ($param->{forceStop
}) {
3096 warn "VM is paused - stop instead of shutdown\n";
3099 die "VM is paused - cannot shutdown\n";
3103 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3105 my $timeout = $param->{timeout
} // 60;
3109 print "Requesting HA stop for VM $vmid\n";
3111 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
3112 PVE
::Tools
::run_command
($cmd);
3116 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3123 syslog
('info', "shutdown VM $vmid: $upid\n");
3125 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
3126 $shutdown, $param->{forceStop
}, $keepActive);
3130 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
3134 __PACKAGE__-
>register_method({
3135 name
=> 'vm_reboot',
3136 path
=> '{vmid}/status/reboot',
3140 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
3142 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3145 additionalProperties
=> 0,
3147 node
=> get_standard_option
('pve-node'),
3148 vmid
=> get_standard_option
('pve-vmid',
3149 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3151 description
=> "Wait maximal timeout seconds for the shutdown.",
3164 my $rpcenv = PVE
::RPCEnvironment
::get
();
3165 my $authuser = $rpcenv->get_user();
3167 my $node = extract_param
($param, 'node');
3168 my $vmid = extract_param
($param, 'vmid');
3170 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid, 1);
3172 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3177 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3178 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3182 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3185 __PACKAGE__-
>register_method({
3186 name
=> 'vm_suspend',
3187 path
=> '{vmid}/status/suspend',
3191 description
=> "Suspend virtual machine.",
3193 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3194 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3195 " on the storage for the vmstate.",
3196 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3199 additionalProperties
=> 0,
3201 node
=> get_standard_option
('pve-node'),
3202 vmid
=> get_standard_option
('pve-vmid',
3203 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3204 skiplock
=> get_standard_option
('skiplock'),
3209 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3211 statestorage
=> get_standard_option
('pve-storage-id', {
3212 description
=> "The storage for the VM state",
3213 requires
=> 'todisk',
3215 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3225 my $rpcenv = PVE
::RPCEnvironment
::get
();
3226 my $authuser = $rpcenv->get_user();
3228 my $node = extract_param
($param, 'node');
3229 my $vmid = extract_param
($param, 'vmid');
3231 my $todisk = extract_param
($param, 'todisk') // 0;
3233 my $statestorage = extract_param
($param, 'statestorage');
3235 my $skiplock = extract_param
($param, 'skiplock');
3236 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3237 if $skiplock && $authuser ne 'root@pam';
3239 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3241 die "Cannot suspend HA managed VM to disk\n"
3242 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3244 # early check for storage permission, for better user feedback
3246 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3247 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3249 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3250 for my $key (keys %$conf) {
3251 next if $key !~ /^hostpci\d+/;
3252 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3253 ." possibility to save/restore their internal state\n";
3256 if (!$statestorage) {
3257 # get statestorage from config if none is given
3258 my $storecfg = PVE
::Storage
::config
();
3259 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3262 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3268 syslog
('info', "suspend VM $vmid: $upid\n");
3270 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3275 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3276 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3279 __PACKAGE__-
>register_method({
3280 name
=> 'vm_resume',
3281 path
=> '{vmid}/status/resume',
3285 description
=> "Resume virtual machine.",
3287 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3290 additionalProperties
=> 0,
3292 node
=> get_standard_option
('pve-node'),
3293 vmid
=> get_standard_option
('pve-vmid',
3294 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3295 skiplock
=> get_standard_option
('skiplock'),
3296 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3306 my $rpcenv = PVE
::RPCEnvironment
::get
();
3308 my $authuser = $rpcenv->get_user();
3310 my $node = extract_param
($param, 'node');
3312 my $vmid = extract_param
($param, 'vmid');
3314 my $skiplock = extract_param
($param, 'skiplock');
3315 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3316 if $skiplock && $authuser ne 'root@pam';
3318 # nocheck is used as part of migration when config file might be still
3320 my $nocheck = extract_param
($param, 'nocheck');
3321 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3322 if $nocheck && $authuser ne 'root@pam';
3324 my $to_disk_suspended;
3326 PVE
::QemuConfig-
>lock_config($vmid, sub {
3327 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3328 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3332 die "VM $vmid not running\n"
3333 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3338 syslog
('info', "resume VM $vmid: $upid\n");
3340 if (!$to_disk_suspended) {
3341 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3343 my $storecfg = PVE
::Storage
::config
();
3344 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3350 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3353 __PACKAGE__-
>register_method({
3354 name
=> 'vm_sendkey',
3355 path
=> '{vmid}/sendkey',
3359 description
=> "Send key event to virtual machine.",
3361 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3364 additionalProperties
=> 0,
3366 node
=> get_standard_option
('pve-node'),
3367 vmid
=> get_standard_option
('pve-vmid',
3368 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3369 skiplock
=> get_standard_option
('skiplock'),
3371 description
=> "The key (qemu monitor encoding).",
3376 returns
=> { type
=> 'null'},
3380 my $rpcenv = PVE
::RPCEnvironment
::get
();
3382 my $authuser = $rpcenv->get_user();
3384 my $node = extract_param
($param, 'node');
3386 my $vmid = extract_param
($param, 'vmid');
3388 my $skiplock = extract_param
($param, 'skiplock');
3389 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3390 if $skiplock && $authuser ne 'root@pam';
3392 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3397 __PACKAGE__-
>register_method({
3398 name
=> 'vm_feature',
3399 path
=> '{vmid}/feature',
3403 description
=> "Check if feature for virtual machine is available.",
3405 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3408 additionalProperties
=> 0,
3410 node
=> get_standard_option
('pve-node'),
3411 vmid
=> get_standard_option
('pve-vmid'),
3413 description
=> "Feature to check.",
3415 enum
=> [ 'snapshot', 'clone', 'copy' ],
3417 snapname
=> get_standard_option
('pve-snapshot-name', {
3425 hasFeature
=> { type
=> 'boolean' },
3428 items
=> { type
=> 'string' },
3435 my $node = extract_param
($param, 'node');
3437 my $vmid = extract_param
($param, 'vmid');
3439 my $snapname = extract_param
($param, 'snapname');
3441 my $feature = extract_param
($param, 'feature');
3443 my $running = PVE
::QemuServer
::check_running
($vmid);
3445 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3448 my $snap = $conf->{snapshots
}->{$snapname};
3449 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3452 my $storecfg = PVE
::Storage
::config
();
3454 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3455 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3458 hasFeature
=> $hasFeature,
3459 nodes
=> [ keys %$nodelist ],
3463 __PACKAGE__-
>register_method({
3465 path
=> '{vmid}/clone',
3469 description
=> "Create a copy of virtual machine/template.",
3471 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3472 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3473 "'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet",
3476 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3478 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3479 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3484 additionalProperties
=> 0,
3486 node
=> get_standard_option
('pve-node'),
3487 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3488 newid
=> get_standard_option
('pve-vmid', {
3489 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3490 description
=> 'VMID for the clone.' }),
3493 type
=> 'string', format
=> 'dns-name',
3494 description
=> "Set a name for the new VM.",
3499 description
=> "Description for the new VM.",
3503 type
=> 'string', format
=> 'pve-poolid',
3504 description
=> "Add the new VM to the specified pool.",
3506 snapname
=> get_standard_option
('pve-snapshot-name', {
3509 storage
=> get_standard_option
('pve-storage-id', {
3510 description
=> "Target storage for full clone.",
3514 description
=> "Target format for file storage. Only valid for full clone.",
3517 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3522 description
=> "Create a full copy of all disks. This is always done when " .
3523 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3525 target
=> get_standard_option
('pve-node', {
3526 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3530 description
=> "Override I/O bandwidth limit (in KiB/s).",
3534 default => 'clone limit from datacenter or storage config',
3544 my $rpcenv = PVE
::RPCEnvironment
::get
();
3545 my $authuser = $rpcenv->get_user();
3547 my $node = extract_param
($param, 'node');
3548 my $vmid = extract_param
($param, 'vmid');
3549 my $newid = extract_param
($param, 'newid');
3550 my $pool = extract_param
($param, 'pool');
3552 my $snapname = extract_param
($param, 'snapname');
3553 my $storage = extract_param
($param, 'storage');
3554 my $format = extract_param
($param, 'format');
3555 my $target = extract_param
($param, 'target');
3557 my $localnode = PVE
::INotify
::nodename
();
3559 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3563 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3565 my $load_and_check = sub {
3566 $rpcenv->check_pool_exist($pool) if defined($pool);
3567 PVE
::Cluster
::check_node_exists
($target) if $target;
3569 my $storecfg = PVE
::Storage
::config
();
3572 # check if storage is enabled on local node
3573 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3575 # check if storage is available on target node
3576 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3577 # clone only works if target storage is shared
3578 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3579 die "can't clone to non-shared storage '$storage'\n"
3580 if !$scfg->{shared
};
3584 PVE
::Cluster
::check_cfs_quorum
();
3586 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3587 PVE
::QemuConfig-
>check_lock($conf);
3589 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3590 die "unexpected state change\n" if $verify_running != $running;
3592 die "snapshot '$snapname' does not exist\n"
3593 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3595 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3597 die "parameter 'storage' not allowed for linked clones\n"
3598 if defined($storage) && !$full;
3600 die "parameter 'format' not allowed for linked clones\n"
3601 if defined($format) && !$full;
3603 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3605 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3606 PVE
::QemuServer
::check_mapping_access
($rpcenv, $authuser, $oldconf);
3608 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $oldconf);
3610 die "can't clone VM to node '$target' (VM uses local storage)\n"
3611 if $target && !$sharedvm;
3613 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3614 die "unable to create VM $newid: config file already exists\n"
3617 my $newconf = { lock => 'clone' };
3622 foreach my $opt (keys %$oldconf) {
3623 my $value = $oldconf->{$opt};
3625 # do not copy snapshot related info
3626 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3627 $opt eq 'vmstate' || $opt eq 'snapstate';
3629 # no need to copy unused images, because VMID(owner) changes anyways
3630 next if $opt =~ m/^unused\d+$/;
3632 die "cannot clone TPM state while VM is running\n"
3633 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3635 # always change MAC! address
3636 if ($opt =~ m/^net(\d+)$/) {
3637 my $net = PVE
::QemuServer
::parse_net
($value);
3638 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3639 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3640 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3641 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3642 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3643 die "unable to parse drive options for '$opt'\n" if !$drive;
3644 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3645 $newconf->{$opt} = $value; # simply copy configuration
3647 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3648 die "Full clone feature is not supported for drive '$opt'\n"
3649 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3650 $fullclone->{$opt} = 1;
3652 # not full means clone instead of copy
3653 die "Linked clone feature is not supported for drive '$opt'\n"
3654 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3656 $drives->{$opt} = $drive;
3657 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3658 push @$vollist, $drive->{file
};
3661 # copy everything else
3662 $newconf->{$opt} = $value;
3666 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3670 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3671 my $storecfg = PVE
::Storage
::config
();
3673 # auto generate a new uuid
3674 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3675 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3676 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3677 # auto generate a new vmgenid only if the option was set for template
3678 if ($newconf->{vmgenid
}) {
3679 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3682 delete $newconf->{template
};
3684 if ($param->{name
}) {
3685 $newconf->{name
} = $param->{name
};
3687 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3690 if ($param->{description
}) {
3691 $newconf->{description
} = $param->{description
};
3694 # create empty/temp config - this fails if VM already exists on other node
3695 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3696 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3698 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3700 my $newvollist = [];
3707 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3709 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3711 my $bwlimit = extract_param
($param, 'bwlimit');
3713 my $total_jobs = scalar(keys %{$drives});
3716 foreach my $opt (sort keys %$drives) {
3717 my $drive = $drives->{$opt};
3718 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3719 my $completion = $skipcomplete ?
'skip' : 'complete';
3721 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3722 my $storage_list = [ $src_sid ];
3723 push @$storage_list, $storage if defined($storage);
3724 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3728 running
=> $running,
3731 snapname
=> $snapname,
3737 storage
=> $storage,
3741 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3742 if $opt eq 'efidisk0';
3744 my $newdrive = PVE
::QemuServer
::clone_disk
(
3756 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3758 PVE
::QemuConfig-
>write_config($newid, $newconf);
3762 delete $newconf->{lock};
3764 # do not write pending changes
3765 if (my @changes = keys %{$newconf->{pending
}}) {
3766 my $pending = join(',', @changes);
3767 warn "found pending changes for '$pending', discarding for clone\n";
3768 delete $newconf->{pending
};
3771 PVE
::QemuConfig-
>write_config($newid, $newconf);
3774 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3775 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3776 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3778 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3779 die "Failed to move config to node '$target' - rename failed: $!\n"
3780 if !rename($conffile, $newconffile);
3783 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3786 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3787 sleep 1; # some storage like rbd need to wait before release volume - really?
3789 foreach my $volid (@$newvollist) {
3790 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3794 PVE
::Firewall
::remove_vmfw_conf
($newid);
3796 unlink $conffile; # avoid races -> last thing before die
3798 die "clone failed: $err";
3804 # Aquire exclusive lock lock for $newid
3805 my $lock_target_vm = sub {
3806 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3809 my $lock_source_vm = sub {
3810 # exclusive lock if VM is running - else shared lock is enough;
3812 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3814 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3818 $load_and_check->(); # early checks before forking/locking
3820 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3823 __PACKAGE__-
>register_method({
3824 name
=> 'move_vm_disk',
3825 path
=> '{vmid}/move_disk',
3829 description
=> "Move volume to different storage or to a different VM.",
3831 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3832 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3833 "a disk to another VM, you need the permissions on the target VM as well.",
3834 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3837 additionalProperties
=> 0,
3839 node
=> get_standard_option
('pve-node'),
3840 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3841 'target-vmid' => get_standard_option
('pve-vmid', {
3842 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3847 description
=> "The disk you want to move.",
3848 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3850 storage
=> get_standard_option
('pve-storage-id', {
3851 description
=> "Target storage.",
3852 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3857 description
=> "Target Format.",
3858 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3863 description
=> "Delete the original disk after successful copy. By default the"
3864 ." original disk is kept as unused disk.",
3870 description
=> 'Prevent changes if current configuration file has different SHA1"
3871 ." digest. This can be used to prevent concurrent modifications.',
3876 description
=> "Override I/O bandwidth limit (in KiB/s).",
3880 default => 'move limit from datacenter or storage config',
3884 description
=> "The config key the disk will be moved to on the target VM"
3885 ." (for example, ide0 or scsi1). Default is the source disk key.",
3886 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3889 'target-digest' => {
3891 description
=> 'Prevent changes if the current config file of the target VM has a"
3892 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3900 description
=> "the task ID.",
3905 my $rpcenv = PVE
::RPCEnvironment
::get
();
3906 my $authuser = $rpcenv->get_user();
3908 my $node = extract_param
($param, 'node');
3909 my $vmid = extract_param
($param, 'vmid');
3910 my $target_vmid = extract_param
($param, 'target-vmid');
3911 my $digest = extract_param
($param, 'digest');
3912 my $target_digest = extract_param
($param, 'target-digest');
3913 my $disk = extract_param
($param, 'disk');
3914 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3915 my $storeid = extract_param
($param, 'storage');
3916 my $format = extract_param
($param, 'format');
3918 my $storecfg = PVE
::Storage
::config
();
3920 my $load_and_check_move = sub {
3921 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3922 PVE
::QemuConfig-
>check_lock($conf);
3924 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3926 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3928 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3930 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3931 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3933 my $old_volid = $drive->{file
};
3935 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3936 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3940 die "you can't move to the same storage with same format\n"
3941 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3943 # this only checks snapshots because $disk is passed!
3944 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3950 die "you can't move a disk with snapshots and delete the source\n"
3951 if $snapshotted && $param->{delete};
3953 return ($conf, $drive, $oldstoreid, $snapshotted);
3956 my $move_updatefn = sub {
3957 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3958 my $old_volid = $drive->{file
};
3960 PVE
::Cluster
::log_msg
(
3963 "move disk VM $vmid: move --disk $disk --storage $storeid"
3966 my $running = PVE
::QemuServer
::check_running
($vmid);
3968 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3970 my $newvollist = [];
3976 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3978 warn "moving disk with snapshots, snapshots will not be moved!\n"
3981 my $bwlimit = extract_param
($param, 'bwlimit');
3982 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3984 [$oldstoreid, $storeid],
3990 running
=> $running,
3999 storage
=> $storeid,
4003 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
4004 if $disk eq 'efidisk0';
4006 my $newdrive = PVE
::QemuServer
::clone_disk
(
4017 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
4019 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
4021 # convert moved disk to base if part of template
4022 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
4023 if PVE
::QemuConfig-
>is_template($conf);
4025 PVE
::QemuConfig-
>write_config($vmid, $conf);
4027 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
4028 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
4029 eval { mon_cmd
($vmid, "guest-fstrim") };
4033 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
4034 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
4040 foreach my $volid (@$newvollist) {
4041 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
4044 die "storage migration failed: $err";
4047 if ($param->{delete}) {
4049 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
4050 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
4056 my $load_and_check_reassign_configs = sub {
4057 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
4059 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
4060 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
4062 my $source_node = $vmlist->{$vmid}->{node
};
4063 my $target_node = $vmlist->{$target_vmid}->{node
};
4065 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
4066 if $source_node ne $target_node;
4068 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
4069 PVE
::QemuConfig-
>check_lock($source_conf);
4070 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
4071 PVE
::QemuConfig-
>check_lock($target_conf);
4073 die "Can't move disks from or to template VMs\n"
4074 if ($source_conf->{template
} || $target_conf->{template
});
4077 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
4078 die "VM ${vmid}: $@" if $@;
4081 if ($target_digest) {
4082 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
4083 die "VM ${target_vmid}: $@" if $@;
4086 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
4088 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
4089 if $target_conf->{$target_disk};
4091 my $drive = PVE
::QemuServer
::parse_drive
(
4093 $source_conf->{$disk},
4095 die "failed to parse source disk - $@\n" if !$drive;
4097 my $source_volid = $drive->{file
};
4099 die "disk '${disk}' has no associated volume\n" if !$source_volid;
4100 die "CD drive contents can't be moved to another VM\n"
4101 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
4103 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
4104 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
4106 die "Can't move disk used by a snapshot to another VM\n"
4107 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
4108 die "Storage does not support moving of this disk to another VM\n"
4109 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
4110 die "Cannot move disk to another VM while the source VM is running - detach first\n"
4111 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
4113 # now re-parse using target disk slot format
4114 if ($target_disk =~ /^unused\d+$/) {
4115 $drive = PVE
::QemuServer
::parse_drive
(
4120 $drive = PVE
::QemuServer
::parse_drive
(
4122 $source_conf->{$disk},
4125 die "failed to parse source disk for target disk format - $@\n" if !$drive;
4127 my $repl_conf = PVE
::ReplicationConfig-
>new();
4128 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
4129 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4130 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
4131 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
4134 return ($source_conf, $target_conf, $drive);
4139 print STDERR
"$msg\n";
4142 my $disk_reassignfn = sub {
4143 return PVE
::QemuConfig-
>lock_config($vmid, sub {
4144 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
4145 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
4147 my $source_volid = $drive->{file
};
4149 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
4150 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
4152 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4154 my $new_volid = PVE
::Storage
::rename_volume
(
4160 $drive->{file
} = $new_volid;
4162 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
4163 if (defined(delete $boot_order->{$disk})) {
4164 print "removing disk '$disk' from boot order config\n";
4165 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
4166 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
4169 delete $source_conf->{$disk};
4170 print "removing disk '${disk}' from VM '${vmid}' config\n";
4171 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
4173 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
4175 if ($target_disk =~ /^unused\d+$/) {
4176 $target_conf->{$target_disk} = $drive_string;
4177 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4182 vmid
=> $target_vmid,
4183 digest
=> $target_digest,
4184 $target_disk => $drive_string,
4190 # remove possible replication snapshots
4191 if (PVE
::Storage
::volume_has_feature
(
4197 PVE
::Replication
::prepare
(
4207 print "Failed to remove replication snapshots on moved disk " .
4208 "'$target_disk'. Manual cleanup could be necessary.\n";
4215 if ($target_vmid && $storeid) {
4216 my $msg = "either set 'storage' or 'target-vmid', but not both";
4217 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4218 } elsif ($target_vmid) {
4219 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4220 if $authuser ne 'root@pam';
4222 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4223 if $vmid eq $target_vmid;
4225 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4226 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4227 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4229 return $rpcenv->fork_worker(
4231 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4235 } elsif ($storeid) {
4236 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4238 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4239 if $disk =~ m/^unused\d+$/;
4241 $load_and_check_move->(); # early checks before forking/locking
4244 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4247 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4249 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4250 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4254 my $check_vm_disks_local = sub {
4255 my ($storecfg, $vmconf, $vmid) = @_;
4257 my $local_disks = {};
4259 # add some more information to the disks e.g. cdrom
4260 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4261 my ($volid, $attr) = @_;
4263 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4265 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4266 return if $scfg->{shared
};
4268 # The shared attr here is just a special case where the vdisk
4269 # is marked as shared manually
4270 return if $attr->{shared
};
4271 return if $attr->{cdrom
} and $volid eq "none";
4273 if (exists $local_disks->{$volid}) {
4274 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4276 $local_disks->{$volid} = $attr;
4277 # ensure volid is present in case it's needed
4278 $local_disks->{$volid}->{volid
} = $volid;
4282 return $local_disks;
4285 __PACKAGE__-
>register_method({
4286 name
=> 'migrate_vm_precondition',
4287 path
=> '{vmid}/migrate',
4291 description
=> "Get preconditions for migration.",
4293 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4296 additionalProperties
=> 0,
4298 node
=> get_standard_option
('pve-node'),
4299 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4300 target
=> get_standard_option
('pve-node', {
4301 description
=> "Target node.",
4302 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4310 running
=> { type
=> 'boolean' },
4314 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4316 not_allowed_nodes
=> {
4319 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4323 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4325 local_resources
=> {
4327 description
=> "List local resources e.g. pci, usb"
4329 'mapped-resources' => {
4331 description
=> "List of mapped resources e.g. pci, usb"
4338 my $rpcenv = PVE
::RPCEnvironment
::get
();
4340 my $authuser = $rpcenv->get_user();
4342 PVE
::Cluster
::check_cfs_quorum
();
4346 my $vmid = extract_param
($param, 'vmid');
4347 my $target = extract_param
($param, 'target');
4348 my $localnode = PVE
::INotify
::nodename
();
4352 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4353 my $storecfg = PVE
::Storage
::config
();
4356 # try to detect errors early
4357 PVE
::QemuConfig-
>check_lock($vmconf);
4359 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4361 my ($local_resources, $mapped_resources, $missing_mappings_by_node) =
4362 PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4363 delete $missing_mappings_by_node->{$localnode};
4365 # if vm is not running, return target nodes where local storage/mapped devices are available
4366 # for offline migration
4367 if (!$res->{running
}) {
4368 $res->{allowed_nodes
} = [];
4369 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4370 delete $checked_nodes->{$localnode};
4372 foreach my $node (keys %$checked_nodes) {
4373 my $missing_mappings = $missing_mappings_by_node->{$node};
4374 if (scalar($missing_mappings->@*)) {
4375 $checked_nodes->{$node}->{'unavailable-resources'} = $missing_mappings;
4379 if (!defined($checked_nodes->{$node}->{unavailable_storages
})) {
4380 push @{$res->{allowed_nodes
}}, $node;
4384 $res->{not_allowed_nodes
} = $checked_nodes;
4387 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4388 $res->{local_disks
} = [ values %$local_disks ];;
4390 $res->{local_resources
} = $local_resources;
4391 $res->{'mapped-resources'} = $mapped_resources;
4398 __PACKAGE__-
>register_method({
4399 name
=> 'migrate_vm',
4400 path
=> '{vmid}/migrate',
4404 description
=> "Migrate virtual machine. Creates a new migration task.",
4406 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4409 additionalProperties
=> 0,
4411 node
=> get_standard_option
('pve-node'),
4412 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4413 target
=> get_standard_option
('pve-node', {
4414 description
=> "Target node.",
4415 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4419 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4424 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4429 enum
=> ['secure', 'insecure'],
4430 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4433 migration_network
=> {
4434 type
=> 'string', format
=> 'CIDR',
4435 description
=> "CIDR of the (sub) network that is used for migration.",
4438 "with-local-disks" => {
4440 description
=> "Enable live storage migration for local disk",
4443 targetstorage
=> get_standard_option
('pve-targetstorage', {
4444 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4447 description
=> "Override I/O bandwidth limit (in KiB/s).",
4451 default => 'migrate limit from datacenter or storage config',
4457 description
=> "the task ID.",
4462 my $rpcenv = PVE
::RPCEnvironment
::get
();
4463 my $authuser = $rpcenv->get_user();
4465 my $target = extract_param
($param, 'target');
4467 my $localnode = PVE
::INotify
::nodename
();
4468 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4470 PVE
::Cluster
::check_cfs_quorum
();
4472 PVE
::Cluster
::check_node_exists
($target);
4474 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4476 my $vmid = extract_param
($param, 'vmid');
4478 raise_param_exc
({ force
=> "Only root may use this option." })
4479 if $param->{force
} && $authuser ne 'root@pam';
4481 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4482 if $param->{migration_type
} && $authuser ne 'root@pam';
4484 # allow root only until better network permissions are available
4485 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4486 if $param->{migration_network
} && $authuser ne 'root@pam';
4489 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4491 # try to detect errors early
4493 PVE
::QemuConfig-
>check_lock($conf);
4495 if (PVE
::QemuServer
::check_running
($vmid)) {
4496 die "can't migrate running VM without --online\n" if !$param->{online
};
4498 my $repl_conf = PVE
::ReplicationConfig-
>new();
4499 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4500 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4501 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4502 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4503 "target. Use 'force' to override.\n";
4506 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4507 $param->{online
} = 0;
4510 my $storecfg = PVE
::Storage
::config
();
4511 if (my $targetstorage = $param->{targetstorage
}) {
4512 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4513 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4516 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4517 if !defined($storagemap->{identity
});
4519 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4520 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4523 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4524 if $storagemap->{default};
4526 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4527 if $storagemap->{identity
};
4529 $param->{storagemap
} = $storagemap;
4531 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4534 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4539 print "Requesting HA migration for VM $vmid to node $target\n";
4541 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4542 PVE
::Tools
::run_command
($cmd);
4546 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4551 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4555 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4558 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4563 __PACKAGE__-
>register_method({
4564 name
=> 'remote_migrate_vm',
4565 path
=> '{vmid}/remote_migrate',
4569 description
=> "Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!",
4571 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4574 additionalProperties
=> 0,
4576 node
=> get_standard_option
('pve-node'),
4577 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4578 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
4579 'target-endpoint' => get_standard_option
('proxmox-remote', {
4580 description
=> "Remote target endpoint",
4584 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4589 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.",
4593 'target-storage' => get_standard_option
('pve-targetstorage', {
4594 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4597 'target-bridge' => {
4599 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.",
4600 format
=> 'bridge-pair-list',
4603 description
=> "Override I/O bandwidth limit (in KiB/s).",
4607 default => 'migrate limit from datacenter or storage config',
4613 description
=> "the task ID.",
4618 my $rpcenv = PVE
::RPCEnvironment
::get
();
4619 my $authuser = $rpcenv->get_user();
4621 my $source_vmid = extract_param
($param, 'vmid');
4622 my $target_endpoint = extract_param
($param, 'target-endpoint');
4623 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
4625 my $delete = extract_param
($param, 'delete') // 0;
4627 PVE
::Cluster
::check_cfs_quorum
();
4630 my $conf = PVE
::QemuConfig-
>load_config($source_vmid);
4632 PVE
::QemuConfig-
>check_lock($conf);
4634 raise_param_exc
({ vmid
=> "cannot migrate HA-managed VM to remote cluster" })
4635 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
4637 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
4639 # TODO: move this as helper somewhere appropriate?
4641 protocol
=> 'https',
4642 host
=> $remote->{host
},
4643 port
=> $remote->{port
} // 8006,
4644 apitoken
=> $remote->{apitoken
},
4648 if ($fp = $remote->{fingerprint
}) {
4649 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
4652 print "Establishing API connection with remote at '$remote->{host}'\n";
4654 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
4656 if (!defined($fp)) {
4657 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
4658 foreach my $cert (@$cert_info) {
4659 my $filename = $cert->{filename
};
4660 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
4661 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
4663 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
4667 my $repl_conf = PVE
::ReplicationConfig-
>new();
4668 my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1);
4669 die "cannot remote-migrate replicated VM\n" if $is_replicated;
4671 if (PVE
::QemuServer
::check_running
($source_vmid)) {
4672 die "can't migrate running VM without --online\n" if !$param->{online
};
4675 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4676 $param->{online
} = 0;
4679 my $storecfg = PVE
::Storage
::config
();
4680 my $target_storage = extract_param
($param, 'target-storage');
4681 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
4682 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
4685 my $target_bridge = extract_param
($param, 'target-bridge');
4686 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
4687 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
4690 die "remote migration requires explicit storage mapping!\n"
4691 if $storagemap->{identity
};
4693 $param->{storagemap
} = $storagemap;
4694 $param->{bridgemap
} = $bridgemap;
4695 $param->{remote
} = {
4696 conn
=> $conn_args, # re-use fingerprint for tunnel
4697 client
=> $api_client,
4698 vmid
=> $target_vmid,
4700 $param->{migration_type
} = 'websocket';
4701 $param->{'with-local-disks'} = 1;
4702 $param->{delete} = $delete if $delete;
4704 my $cluster_status = $api_client->get("/cluster/status");
4706 foreach my $entry (@$cluster_status) {
4707 next if $entry->{type
} ne 'node';
4708 if ($entry->{local}) {
4709 $target_node = $entry->{name
};
4714 die "couldn't determine endpoint's node name\n"
4715 if !defined($target_node);
4718 PVE
::QemuMigrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
4722 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
4725 return $rpcenv->fork_worker('qmigrate', $source_vmid, $authuser, $worker);
4728 __PACKAGE__-
>register_method({
4730 path
=> '{vmid}/monitor',
4734 description
=> "Execute QEMU monitor commands.",
4736 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4737 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4740 additionalProperties
=> 0,
4742 node
=> get_standard_option
('pve-node'),
4743 vmid
=> get_standard_option
('pve-vmid'),
4746 description
=> "The monitor command.",
4750 returns
=> { type
=> 'string'},
4754 my $rpcenv = PVE
::RPCEnvironment
::get
();
4755 my $authuser = $rpcenv->get_user();
4758 my $command = shift;
4759 return $command =~ m/^\s*info(\s+|$)/
4760 || $command =~ m/^\s*help\s*$/;
4763 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4764 if !&$is_ro($param->{command
});
4766 my $vmid = $param->{vmid
};
4768 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4772 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4774 $res = "ERROR: $@" if $@;
4779 __PACKAGE__-
>register_method({
4780 name
=> 'resize_vm',
4781 path
=> '{vmid}/resize',
4785 description
=> "Extend volume size.",
4787 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4790 additionalProperties
=> 0,
4792 node
=> get_standard_option
('pve-node'),
4793 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4794 skiplock
=> get_standard_option
('skiplock'),
4797 description
=> "The disk you want to resize.",
4798 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4802 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4803 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.",
4807 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4815 description
=> "the task ID.",
4820 my $rpcenv = PVE
::RPCEnvironment
::get
();
4822 my $authuser = $rpcenv->get_user();
4824 my $node = extract_param
($param, 'node');
4826 my $vmid = extract_param
($param, 'vmid');
4828 my $digest = extract_param
($param, 'digest');
4830 my $disk = extract_param
($param, 'disk');
4832 my $sizestr = extract_param
($param, 'size');
4834 my $skiplock = extract_param
($param, 'skiplock');
4835 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4836 if $skiplock && $authuser ne 'root@pam';
4838 my $storecfg = PVE
::Storage
::config
();
4840 my $updatefn = sub {
4842 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4844 die "checksum missmatch (file change by other user?)\n"
4845 if $digest && $digest ne $conf->{digest
};
4846 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4848 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4850 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4852 my (undef, undef, undef, undef, undef, undef, $format) =
4853 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4855 my $volid = $drive->{file
};
4857 die "disk '$disk' has no associated volume\n" if !$volid;
4859 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4861 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4863 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4865 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4866 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4868 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4870 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4871 my ($ext, $newsize, $unit) = ($1, $2, $4);
4874 $newsize = $newsize * 1024;
4875 } elsif ($unit eq 'M') {
4876 $newsize = $newsize * 1024 * 1024;
4877 } elsif ($unit eq 'G') {
4878 $newsize = $newsize * 1024 * 1024 * 1024;
4879 } elsif ($unit eq 'T') {
4880 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4883 $newsize += $size if $ext;
4884 $newsize = int($newsize);
4886 die "shrinking disks is not supported\n" if $newsize < $size;
4888 return if $size == $newsize;
4890 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4892 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4894 $drive->{size
} = $newsize;
4895 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4897 PVE
::QemuConfig-
>write_config($vmid, $conf);
4901 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4904 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
4907 __PACKAGE__-
>register_method({
4908 name
=> 'snapshot_list',
4909 path
=> '{vmid}/snapshot',
4911 description
=> "List all snapshots.",
4913 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4916 protected
=> 1, # qemu pid files are only readable by root
4918 additionalProperties
=> 0,
4920 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4921 node
=> get_standard_option
('pve-node'),
4930 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4934 description
=> "Snapshot includes RAM.",
4939 description
=> "Snapshot description.",
4943 description
=> "Snapshot creation time",
4945 renderer
=> 'timestamp',
4949 description
=> "Parent snapshot identifier.",
4955 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4960 my $vmid = $param->{vmid
};
4962 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4963 my $snaphash = $conf->{snapshots
} || {};
4967 foreach my $name (keys %$snaphash) {
4968 my $d = $snaphash->{$name};
4971 snaptime
=> $d->{snaptime
} || 0,
4972 vmstate
=> $d->{vmstate
} ?
1 : 0,
4973 description
=> $d->{description
} || '',
4975 $item->{parent
} = $d->{parent
} if $d->{parent
};
4976 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4980 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4983 digest
=> $conf->{digest
},
4984 running
=> $running,
4985 description
=> "You are here!",
4987 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4989 push @$res, $current;
4994 __PACKAGE__-
>register_method({
4996 path
=> '{vmid}/snapshot',
5000 description
=> "Snapshot a VM.",
5002 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5005 additionalProperties
=> 0,
5007 node
=> get_standard_option
('pve-node'),
5008 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5009 snapname
=> get_standard_option
('pve-snapshot-name'),
5013 description
=> "Save the vmstate",
5018 description
=> "A textual description or comment.",
5024 description
=> "the task ID.",
5029 my $rpcenv = PVE
::RPCEnvironment
::get
();
5031 my $authuser = $rpcenv->get_user();
5033 my $node = extract_param
($param, 'node');
5035 my $vmid = extract_param
($param, 'vmid');
5037 my $snapname = extract_param
($param, 'snapname');
5039 die "unable to use snapshot name 'current' (reserved name)\n"
5040 if $snapname eq 'current';
5042 die "unable to use snapshot name 'pending' (reserved name)\n"
5043 if lc($snapname) eq 'pending';
5046 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
5047 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
5048 $param->{description
});
5051 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
5054 __PACKAGE__-
>register_method({
5055 name
=> 'snapshot_cmd_idx',
5056 path
=> '{vmid}/snapshot/{snapname}',
5063 additionalProperties
=> 0,
5065 vmid
=> get_standard_option
('pve-vmid'),
5066 node
=> get_standard_option
('pve-node'),
5067 snapname
=> get_standard_option
('pve-snapshot-name'),
5076 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
5083 push @$res, { cmd
=> 'rollback' };
5084 push @$res, { cmd
=> 'config' };
5089 __PACKAGE__-
>register_method({
5090 name
=> 'update_snapshot_config',
5091 path
=> '{vmid}/snapshot/{snapname}/config',
5095 description
=> "Update snapshot metadata.",
5097 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5100 additionalProperties
=> 0,
5102 node
=> get_standard_option
('pve-node'),
5103 vmid
=> get_standard_option
('pve-vmid'),
5104 snapname
=> get_standard_option
('pve-snapshot-name'),
5108 description
=> "A textual description or comment.",
5112 returns
=> { type
=> 'null' },
5116 my $rpcenv = PVE
::RPCEnvironment
::get
();
5118 my $authuser = $rpcenv->get_user();
5120 my $vmid = extract_param
($param, 'vmid');
5122 my $snapname = extract_param
($param, 'snapname');
5124 return if !defined($param->{description
});
5126 my $updatefn = sub {
5128 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5130 PVE
::QemuConfig-
>check_lock($conf);
5132 my $snap = $conf->{snapshots
}->{$snapname};
5134 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5136 $snap->{description
} = $param->{description
} if defined($param->{description
});
5138 PVE
::QemuConfig-
>write_config($vmid, $conf);
5141 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
5146 __PACKAGE__-
>register_method({
5147 name
=> 'get_snapshot_config',
5148 path
=> '{vmid}/snapshot/{snapname}/config',
5151 description
=> "Get snapshot configuration",
5153 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
5156 additionalProperties
=> 0,
5158 node
=> get_standard_option
('pve-node'),
5159 vmid
=> get_standard_option
('pve-vmid'),
5160 snapname
=> get_standard_option
('pve-snapshot-name'),
5163 returns
=> { type
=> "object" },
5167 my $rpcenv = PVE
::RPCEnvironment
::get
();
5169 my $authuser = $rpcenv->get_user();
5171 my $vmid = extract_param
($param, 'vmid');
5173 my $snapname = extract_param
($param, 'snapname');
5175 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5177 my $snap = $conf->{snapshots
}->{$snapname};
5179 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5184 __PACKAGE__-
>register_method({
5186 path
=> '{vmid}/snapshot/{snapname}/rollback',
5190 description
=> "Rollback VM state to specified snapshot.",
5192 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
5195 additionalProperties
=> 0,
5197 node
=> get_standard_option
('pve-node'),
5198 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5199 snapname
=> get_standard_option
('pve-snapshot-name'),
5202 description
=> "Whether the VM should get started after rolling back successfully."
5203 . " (Note: VMs will be automatically started if the snapshot includes RAM.)",
5211 description
=> "the task ID.",
5216 my $rpcenv = PVE
::RPCEnvironment
::get
();
5218 my $authuser = $rpcenv->get_user();
5220 my $node = extract_param
($param, 'node');
5222 my $vmid = extract_param
($param, 'vmid');
5224 my $snapname = extract_param
($param, 'snapname');
5227 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
5228 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
5230 if ($param->{start
} && !PVE
::QemuServer
::Helpers
::vm_running_locally
($vmid)) {
5231 PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node });
5236 # hold migration lock, this makes sure that nobody create replication snapshots
5237 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
5240 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
5243 __PACKAGE__-
>register_method({
5244 name
=> 'delsnapshot',
5245 path
=> '{vmid}/snapshot/{snapname}',
5249 description
=> "Delete a VM snapshot.",
5251 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5254 additionalProperties
=> 0,
5256 node
=> get_standard_option
('pve-node'),
5257 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5258 snapname
=> get_standard_option
('pve-snapshot-name'),
5262 description
=> "For removal from config file, even if removing disk snapshots fails.",
5268 description
=> "the task ID.",
5273 my $rpcenv = PVE
::RPCEnvironment
::get
();
5275 my $authuser = $rpcenv->get_user();
5277 my $node = extract_param
($param, 'node');
5279 my $vmid = extract_param
($param, 'vmid');
5281 my $snapname = extract_param
($param, 'snapname');
5284 my $do_delete = sub {
5286 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
5287 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
5291 if ($param->{force
}) {
5294 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
5296 die $err if $lock_obtained;
5297 die "Failed to obtain guest migration lock - replication running?\n";
5302 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
5305 __PACKAGE__-
>register_method({
5307 path
=> '{vmid}/template',
5311 description
=> "Create a Template.",
5313 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
5314 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
5317 additionalProperties
=> 0,
5319 node
=> get_standard_option
('pve-node'),
5320 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
5324 description
=> "If you want to convert only 1 disk to base image.",
5325 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
5332 description
=> "the task ID.",
5337 my $rpcenv = PVE
::RPCEnvironment
::get
();
5339 my $authuser = $rpcenv->get_user();
5341 my $node = extract_param
($param, 'node');
5343 my $vmid = extract_param
($param, 'vmid');
5345 my $disk = extract_param
($param, 'disk');
5347 my $load_and_check = sub {
5348 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5350 PVE
::QemuConfig-
>check_lock($conf);
5352 die "unable to create template, because VM contains snapshots\n"
5353 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
5355 die "you can't convert a template to a template\n"
5356 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
5358 die "you can't convert a VM to template if VM is running\n"
5359 if PVE
::QemuServer
::check_running
($vmid);
5364 $load_and_check->();
5367 PVE
::QemuConfig-
>lock_config($vmid, sub {
5368 my $conf = $load_and_check->();
5370 $conf->{template
} = 1;
5371 PVE
::QemuConfig-
>write_config($vmid, $conf);
5373 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5377 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5380 __PACKAGE__-
>register_method({
5381 name
=> 'cloudinit_generated_config_dump',
5382 path
=> '{vmid}/cloudinit/dump',
5385 description
=> "Get automatically generated cloudinit config.",
5387 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5390 additionalProperties
=> 0,
5392 node
=> get_standard_option
('pve-node'),
5393 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5395 description
=> 'Config type.',
5397 enum
=> ['user', 'network', 'meta'],
5407 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5409 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});
5412 __PACKAGE__-
>register_method({
5414 path
=> '{vmid}/mtunnel',
5417 description
=> 'Migration tunnel endpoint - only for internal use by VM migration.',
5421 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
5422 ['perm', '/', [ 'Sys.Incoming' ]],
5424 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
5425 " on '/'. Further permission checks happen during the actual migration.",
5428 additionalProperties
=> 0,
5430 node
=> get_standard_option
('pve-node'),
5431 vmid
=> get_standard_option
('pve-vmid'),
5434 format
=> 'pve-storage-id-list',
5436 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
5440 format
=> 'pve-bridge-id-list',
5442 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
5447 additionalProperties
=> 0,
5449 upid
=> { type
=> 'string' },
5450 ticket
=> { type
=> 'string' },
5451 socket => { type
=> 'string' },
5457 my $rpcenv = PVE
::RPCEnvironment
::get
();
5458 my $authuser = $rpcenv->get_user();
5460 my $node = extract_param
($param, 'node');
5461 my $vmid = extract_param
($param, 'vmid');
5463 my $storages = extract_param
($param, 'storages');
5464 my $bridges = extract_param
($param, 'bridges');
5466 my $nodename = PVE
::INotify
::nodename
();
5468 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5469 if $node ne 'localhost' && $node ne $nodename;
5473 my $storecfg = PVE
::Storage
::config
();
5474 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
5475 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
5478 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
5479 PVE
::Network
::read_bridge_mtu
($bridge);
5482 PVE
::Cluster
::check_cfs_quorum
();
5484 my $lock = 'create';
5485 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, 0, $lock); };
5487 raise_param_exc
({ vmid
=> "unable to create empty VM config - $@"})
5492 storecfg
=> PVE
::Storage
::config
(),
5497 my $run_locked = sub {
5498 my ($code, $params) = @_;
5499 return PVE
::QemuConfig-
>lock_config($state->{vmid
}, sub {
5500 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5502 $state->{conf
} = $conf;
5504 die "Encountered wrong lock - aborting mtunnel command handling.\n"
5505 if $state->{lock} && !PVE
::QemuConfig-
>has_lock($conf, $state->{lock});
5507 return $code->($params);
5515 description
=> 'Full VM config, adapted for target cluster/node',
5517 'firewall-config' => {
5519 description
=> 'VM firewall config',
5524 format
=> PVE
::JSONSchema
::get_standard_option
('pve-qm-image-format'),
5527 format
=> 'pve-storage-id',
5531 description
=> 'parsed drive information without volid and format',
5537 description
=> 'params passed to vm_start_nolock',
5541 description
=> 'migrate_opts passed to vm_start_nolock',
5547 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
5553 description
=> 'remove VM config and disks, aborting migration',
5557 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
5558 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
5559 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
5562 my $cmd_handlers = {
5564 # compared against other end's version
5565 # bump/reset for breaking changes
5566 # bump/bump for opt-in changes
5568 api
=> $PVE::QemuMigrate
::WS_TUNNEL_VERSION
,
5575 # parse and write out VM FW config if given
5576 if (my $fw_conf = $params->{'firewall-config'}) {
5577 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
5584 ipset_comments
=> {},
5586 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
5588 # TODO: add flag for strict parsing?
5589 # TODO: add import sub that does all this given raw content?
5590 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
5591 $vmfw_conf->{vmid
} = $state->{vmid
};
5592 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
5594 $state->{cleanup
}->{fw
} = 1;
5597 my $conf_fn = "incoming/qemu-server/$state->{vmid}.conf";
5598 my $new_conf = PVE
::QemuServer
::parse_vm_config
($conf_fn, $params->{conf
}, 1);
5599 delete $new_conf->{lock};
5600 delete $new_conf->{digest
};
5602 # TODO handle properly?
5603 delete $new_conf->{snapshots
};
5604 delete $new_conf->{parent
};
5605 delete $new_conf->{pending
};
5607 # not handled by update_vm_api
5608 my $vmgenid = delete $new_conf->{vmgenid
};
5609 my $meta = delete $new_conf->{meta
};
5610 my $cloudinit = delete $new_conf->{cloudinit
}; # this is informational only
5611 $new_conf->{skip_cloud_init
} = 1; # re-use image from source side
5613 $new_conf->{vmid
} = $state->{vmid
};
5614 $new_conf->{node
} = $node;
5616 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, 'create');
5619 $update_vm_api->($new_conf, 1);
5622 # revert to locked previous config
5623 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5624 $conf->{lock} = 'create';
5625 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5630 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5631 $conf->{lock} = 'migrate';
5632 $conf->{vmgenid
} = $vmgenid if defined($vmgenid);
5633 $conf->{meta
} = $meta if defined($meta);
5634 $conf->{cloudinit
} = $cloudinit if defined($cloudinit);
5635 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5637 $state->{lock} = 'migrate';
5643 return PVE
::StorageTunnel
::handle_bwlimit
($params);
5648 my $format = $params->{format
};
5649 my $storeid = $params->{storage
};
5650 my $drive = $params->{drive
};
5652 $check_storage_access_migrate->($rpcenv, $authuser, $state->{storecfg
}, $storeid, $node);
5655 default => $storeid,
5658 my $source_volumes = {
5668 my $res = PVE
::QemuServer
::vm_migrate_alloc_nbd_disks
($state->{storecfg
}, $state->{vmid
}, $source_volumes, $storagemap);
5669 if (defined($res->{disk
})) {
5670 $state->{cleanup
}->{volumes
}->{$res->{disk
}->{volid
}} = 1;
5671 return $res->{disk
};
5673 die "failed to allocate NBD disk..\n";
5676 'disk-import' => sub {
5679 $check_storage_access_migrate->(
5687 $params->{unix
} = "/run/qemu-server/$state->{vmid}.storage";
5689 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
5691 'query-disk-import' => sub {
5694 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
5699 my $info = PVE
::QemuServer
::vm_start_nolock
(
5703 $params->{start_params
},
5704 $params->{migrate_opts
},
5708 if ($info->{migrate
}->{proto
} ne 'unix') {
5709 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5710 die "migration over non-UNIX sockets not possible\n";
5713 my $socket = $info->{migrate
}->{addr
};
5714 chown $state->{socket_uid
}, -1, $socket;
5715 $state->{sockets
}->{$socket} = 1;
5717 my $unix_sockets = $info->{migrate
}->{unix_sockets
};
5718 foreach my $socket (@$unix_sockets) {
5719 chown $state->{socket_uid
}, -1, $socket;
5720 $state->{sockets
}->{$socket} = 1;
5725 if (PVE
::QemuServer
::qga_check_running
($state->{vmid
})) {
5726 eval { mon_cmd
($state->{vmid
}, "guest-fstrim") };
5727 warn "fstrim failed: $@\n" if $@;
5732 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5736 PVE
::QemuServer
::nbd_stop
($state->{vmid
});
5740 if (PVE
::QemuServer
::Helpers
::vm_running_locally
($state->{vmid
})) {
5741 PVE
::QemuServer
::vm_resume
($state->{vmid
}, 1, 1);
5743 die "VM $state->{vmid} not running\n";
5748 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, $state->{lock});
5749 delete $state->{lock};
5755 my $path = $params->{path
};
5757 die "Not allowed to generate ticket for unknown socket '$path'\n"
5758 if !defined($state->{sockets
}->{$path});
5760 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
5765 if ($params->{cleanup
}) {
5766 if ($state->{cleanup
}->{fw
}) {
5767 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
5770 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
5771 print "freeing volume '$volid' as part of cleanup\n";
5772 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
5776 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5779 print "switching to exit-mode, waiting for client to disconnect\n";
5786 my $socket_addr = "/run/qemu-server/$state->{vmid}.mtunnel";
5787 unlink $socket_addr;
5789 $state->{socket} = IO
::Socket
::UNIX-
>new(
5790 Type
=> SOCK_STREAM
(),
5791 Local
=> $socket_addr,
5795 $state->{socket_uid
} = getpwnam('www-data')
5796 or die "Failed to resolve user 'www-data' to numeric UID\n";
5797 chown $state->{socket_uid
}, -1, $socket_addr;
5800 print "mtunnel started\n";
5802 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
5804 warn "Failed to accept tunnel connection - $@\n";
5806 warn "Removing tunnel socket..\n";
5807 unlink $state->{socket};
5809 warn "Removing temporary VM config..\n";
5811 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5814 die "Exiting mtunnel\n";
5817 $state->{conn
} = $conn;
5819 my $reply_err = sub {
5822 my $reply = JSON
::encode_json
({
5823 success
=> JSON
::false
,
5826 $conn->print("$reply\n");
5830 my $reply_ok = sub {
5833 $res->{success
} = JSON
::true
;
5834 my $reply = JSON
::encode_json
($res);
5835 $conn->print("$reply\n");
5839 while (my $line = <$conn>) {
5842 # untaint, we validate below if needed
5843 ($line) = $line =~ /^(.*)$/;
5844 my $parsed = eval { JSON
::decode_json
($line) };
5846 $reply_err->("failed to parse command - $@");
5850 my $cmd = delete $parsed->{cmd
};
5851 if (!defined($cmd)) {
5852 $reply_err->("'cmd' missing");
5853 } elsif ($state->{exit}) {
5854 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
5856 } elsif (my $handler = $cmd_handlers->{$cmd}) {
5857 print "received command '$cmd'\n";
5859 if ($cmd_desc->{$cmd}) {
5860 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
5864 my $res = $run_locked->($handler, $parsed);
5867 $reply_err->("failed to handle '$cmd' command - $@")
5870 $reply_err->("unknown command '$cmd' given");
5874 if ($state->{exit}) {
5875 print "mtunnel exited\n";
5877 die "mtunnel exited unexpectedly\n";
5881 my $socket_addr = "/run/qemu-server/$vmid.mtunnel";
5882 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
5883 my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);
5888 socket => $socket_addr,
5892 __PACKAGE__-
>register_method({
5893 name
=> 'mtunnelwebsocket',
5894 path
=> '{vmid}/mtunnelwebsocket',
5897 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.",
5898 user
=> 'all', # check inside
5900 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
5902 additionalProperties
=> 0,
5904 node
=> get_standard_option
('pve-node'),
5905 vmid
=> get_standard_option
('pve-vmid'),
5908 description
=> "unix socket to forward to",
5912 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
5919 port
=> { type
=> 'string', optional
=> 1 },
5920 socket => { type
=> 'string', optional
=> 1 },
5926 my $rpcenv = PVE
::RPCEnvironment
::get
();
5927 my $authuser = $rpcenv->get_user();
5929 my $nodename = PVE
::INotify
::nodename
();
5930 my $node = extract_param
($param, 'node');
5932 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5933 if $node ne 'localhost' && $node ne $nodename;
5935 my $vmid = $param->{vmid
};
5937 PVE
::QemuConfig-
>load_config($vmid);
5939 my $socket = $param->{socket};
5940 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
5942 return { socket => $socket };