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
;
36 use PVE
::RPCEnvironment
;
37 use PVE
::AccessControl
;
41 use PVE
::API2
::Firewall
::VM
;
42 use PVE
::API2
::Qemu
::Agent
;
43 use PVE
::VZDump
::Plugin
;
44 use PVE
::DataCenterConfig
;
47 use PVE
::StorageTunnel
;
50 if (!$ENV{PVE_GENERATING_DOCS
}) {
51 require PVE
::HA
::Env
::PVE2
;
52 import PVE
::HA
::Env
::PVE2
;
53 require PVE
::HA
::Config
;
54 import PVE
::HA
::Config
;
58 use base
qw(PVE::RESTHandler);
60 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.";
62 my $resolve_cdrom_alias = sub {
65 if (my $value = $param->{cdrom
}) {
66 $value .= ",media=cdrom" if $value !~ m/media=/;
67 $param->{ide2
} = $value;
68 delete $param->{cdrom
};
72 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
73 my $foreach_volume_with_alloc = sub {
74 my ($param, $func) = @_;
76 for my $opt (sort keys $param->%*) {
77 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
79 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
82 $func->($opt, $drive);
86 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
88 my $check_drive_param = sub {
89 my ($param, $storecfg, $extra_checks) = @_;
91 for my $opt (sort keys $param->%*) {
92 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
94 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
95 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
97 if ($drive->{'import-from'}) {
98 if ($drive->{file
} !~ $NEW_DISK_RE || $3 != 0) {
100 $opt => "'import-from' requires special syntax - ".
101 "use <storage ID>:0,import-from=<source>",
105 if ($opt eq 'efidisk0') {
106 for my $required (qw(efitype pre-enrolled-keys)) {
107 if (!defined($drive->{$required})) {
109 $opt => "need to specify '$required' when using 'import-from'",
113 } elsif ($opt eq 'tpmstate0') {
114 raise_param_exc
({ $opt => "need to specify 'version' when using 'import-from'" })
115 if !defined($drive->{version
});
119 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
121 $extra_checks->($drive) if $extra_checks;
123 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive, 1);
127 my $check_storage_access = sub {
128 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
130 $foreach_volume_with_alloc->($settings, sub {
131 my ($ds, $drive) = @_;
133 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
135 my $volid = $drive->{file
};
136 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
138 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
140 } elsif ($isCDROM && ($volid eq 'cdrom')) {
141 $rpcenv->check($authuser, "/", ['Sys.Console']);
142 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
143 my ($storeid, $size) = ($2 || $default_storage, $3);
144 die "no storage ID specified (and no default storage)\n" if !$storeid;
145 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
146 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
147 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
148 if !$scfg->{content
}->{images
};
150 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
152 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
153 raise_param_exc
({ $ds => "content type needs to be 'images' or 'iso'" })
154 if $vtype ne 'images' && $vtype ne 'iso';
158 if (my $src_image = $drive->{'import-from'}) {
160 if (PVE
::Storage
::parse_volume_id
($src_image, 1)) { # PVE-managed volume
161 (my $vtype, undef, $src_vmid) = PVE
::Storage
::parse_volname
($storecfg, $src_image);
162 raise_param_exc
({ $ds => "$src_image has wrong type '$vtype' - not an image" })
163 if $vtype ne 'images';
166 if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()
167 $rpcenv->check($authuser, "/vms/${src_vmid}", ['VM.Clone']);
169 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $src_image);
174 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
175 if defined($settings->{vmstatestorage
});
178 my $check_storage_access_clone = sub {
179 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
183 PVE
::QemuConfig-
>foreach_volume($conf, sub {
184 my ($ds, $drive) = @_;
186 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
188 my $volid = $drive->{file
};
190 return if !$volid || $volid eq 'none';
193 if ($volid eq 'cdrom') {
194 $rpcenv->check($authuser, "/", ['Sys.Console']);
196 # we simply allow access
197 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
198 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
199 $sharedvm = 0 if !$scfg->{shared
};
203 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
204 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
205 $sharedvm = 0 if !$scfg->{shared
};
207 $sid = $storage if $storage;
208 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
212 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
213 if defined($conf->{vmstatestorage
});
218 my $check_storage_access_migrate = sub {
219 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
221 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
223 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
225 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
226 die "storage '$storage' does not support vm images\n"
227 if !$scfg->{content
}->{images
};
230 my $import_from_volid = sub {
231 my ($storecfg, $src_volid, $dest_info, $vollist) = @_;
233 die "could not get size of $src_volid\n"
234 if !PVE
::Storage
::volume_size_info
($storecfg, $src_volid, 10);
236 die "cannot import from cloudinit disk\n"
237 if PVE
::QemuServer
::Drive
::drive_is_cloudinit
({ file
=> $src_volid });
239 my $src_vmid = (PVE
::Storage
::parse_volname
($storecfg, $src_volid))[2];
241 my $src_vm_state = sub {
242 my $exists = $src_vmid && PVE
::Cluster
::get_vmlist
()->{ids
}->{$src_vmid} ?
1 : 0;
246 eval { PVE
::QemuConfig
::assert_config_exists_on_node
($src_vmid); };
247 die "owner VM $src_vmid not on local node\n" if $@;
248 $runs = PVE
::QemuServer
::Helpers
::vm_running_locally
($src_vmid) || 0;
251 return ($exists, $runs);
254 my ($src_vm_exists, $running) = $src_vm_state->();
256 die "cannot import from '$src_volid' - full clone feature is not supported\n"
257 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $src_volid, undef, $running);
260 my ($src_vm_exists_now, $running_now) = $src_vm_state->();
262 die "owner VM $src_vmid changed state unexpectedly\n"
263 if $src_vm_exists_now != $src_vm_exists || $running_now != $running;
265 my $src_conf = $src_vm_exists_now ? PVE
::QemuConfig-
>load_config($src_vmid) : {};
267 my $src_drive = { file
=> $src_volid };
269 PVE
::QemuConfig-
>foreach_volume($src_conf, sub {
270 my ($ds, $drive) = @_;
272 return if $src_drivename;
274 if ($drive->{file
} eq $src_volid) {
276 $src_drivename = $ds;
282 running
=> $running_now,
283 drivename
=> $src_drivename,
288 my ($src_storeid) = PVE
::Storage
::parse_volume_id
($src_volid);
290 return PVE
::QemuServer
::clone_disk
(
299 PVE
::Storage
::get_bandwidth_limit
('clone', [$src_storeid, $dest_info->{storage
}]),
305 $cloned = PVE
::QemuConfig-
>lock_config_full($src_vmid, 30, $clonefn);
306 } elsif ($src_vmid) {
307 $cloned = PVE
::QemuConfig-
>lock_config_shared($src_vmid, 30, $clonefn);
309 $cloned = $clonefn->();
312 return $cloned->@{qw(file size)};
315 # Note: $pool is only needed when creating a VM, because pool permissions
316 # are automatically inherited if VM already exists inside a pool.
317 my $create_disks = sub {
318 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
325 my ($ds, $disk) = @_;
327 my $volid = $disk->{file
};
328 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
330 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
331 delete $disk->{size
};
332 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
333 } elsif (defined($volname) && $volname eq 'cloudinit') {
334 $storeid = $storeid // $default_storage;
335 die "no storage ID specified (and no default storage)\n" if !$storeid;
338 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
339 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
340 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
342 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
345 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
346 my $name = "vm-$vmid-cloudinit";
350 $fmt = $disk->{format
} // "qcow2";
353 $fmt = $disk->{format
} // "raw";
356 # Initial disk created with 4 MB and aligned to 4MB on regeneration
357 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
358 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
359 $disk->{file
} = $volid;
360 $disk->{media
} = 'cdrom';
361 push @$vollist, $volid;
362 delete $disk->{format
}; # no longer needed
363 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
364 print "$ds: successfully created disk '$res->{$ds}'\n";
365 } elsif ($volid =~ $NEW_DISK_RE) {
366 my ($storeid, $size) = ($2 || $default_storage, $3);
367 die "no storage ID specified (and no default storage)\n" if !$storeid;
369 if (my $source = delete $disk->{'import-from'}) {
372 if (PVE
::Storage
::parse_volume_id
($source, 1)) { # PVE-managed volume
377 format
=> $disk->{format
},
380 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf, $disk)
381 if $ds eq 'efidisk0';
383 ($dst_volid, $size) = eval {
384 $import_from_volid->($storecfg, $source, $dest_info, $vollist);
386 die "cannot import from '$source' - $@" if $@;
388 $source = PVE
::Storage
::abs_filesystem_path
($storecfg, $source, 1);
389 $size = PVE
::Storage
::file_size_info
($source);
390 die "could not get file size of $source\n" if !$size;
392 (undef, $dst_volid) = PVE
::QemuServer
::ImportDisk
::do_import
(
398 format
=> $disk->{format
},
399 'skip-config-update' => 1,
402 push @$vollist, $dst_volid;
405 $disk->{file
} = $dst_volid;
406 $disk->{size
} = $size;
407 delete $disk->{format
}; # no longer needed
408 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
410 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
411 my $fmt = $disk->{format
} || $defformat;
413 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
416 if ($ds eq 'efidisk0') {
417 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
418 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
419 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
420 } elsif ($ds eq 'tpmstate0') {
421 # swtpm can only use raw volumes, and uses a fixed size
422 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
423 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
425 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
427 push @$vollist, $volid;
428 $disk->{file
} = $volid;
429 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
430 delete $disk->{format
}; # no longer needed
431 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
434 print "$ds: successfully created disk '$res->{$ds}'\n";
436 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
438 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
439 die "cannot use volume $volid - content type needs to be 'images' or 'iso'"
440 if $vtype ne 'images' && $vtype ne 'iso';
442 if (PVE
::QemuServer
::Drive
::drive_is_cloudinit
($disk)) {
444 my $ci_key = PVE
::QemuConfig-
>has_cloudinit($conf, $ds)
445 || PVE
::QemuConfig-
>has_cloudinit($conf->{pending
} || {}, $ds)
446 || PVE
::QemuConfig-
>has_cloudinit($res, $ds)
448 die "$ds - cloud-init drive is already attached at '$ci_key'\n";
453 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
455 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
456 die "volume $volid does not exist\n" if !$size;
457 $disk->{size
} = $size;
459 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
463 eval { $foreach_volume_with_alloc->($settings, $code); };
465 # free allocated images on error
467 syslog
('err', "VM $vmid creating disks failed");
468 foreach my $volid (@$vollist) {
469 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
475 return ($vollist, $res);
478 my $check_cpu_model_access = sub {
479 my ($rpcenv, $authuser, $new, $existing) = @_;
481 return if !defined($new->{cpu
});
483 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
484 return if !$cpu || !$cpu->{cputype
}; # always allow default
485 my $cputype = $cpu->{cputype
};
487 if ($existing && $existing->{cpu
}) {
488 # changing only other settings doesn't require permissions for CPU model
489 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
490 return if $existingCpu->{cputype
} eq $cputype;
493 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
494 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
509 my $memoryoptions = {
515 my $hwtypeoptions = {
528 my $generaloptions = {
535 'migrate_downtime' => 1,
536 'migrate_speed' => 1,
548 my $vmpoweroptions = {
555 'vmstatestorage' => 1,
558 my $cloudinitoptions = {
568 my $check_vm_create_serial_perm = sub {
569 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
571 return 1 if $authuser eq 'root@pam';
573 foreach my $opt (keys %{$param}) {
574 next if $opt !~ m/^serial\d+$/;
576 if ($param->{$opt} eq 'socket') {
577 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
579 die "only root can set '$opt' config for real devices\n";
586 my $check_vm_create_usb_perm = sub {
587 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
589 return 1 if $authuser eq 'root@pam';
591 foreach my $opt (keys %{$param}) {
592 next if $opt !~ m/^usb\d+$/;
594 if ($param->{$opt} =~ m/spice/) {
595 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
597 die "only root can set '$opt' config for real devices\n";
604 my $check_vm_modify_config_perm = sub {
605 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
607 return 1 if $authuser eq 'root@pam';
609 foreach my $opt (@$key_list) {
610 # some checks (e.g., disk, serial port, usb) need to be done somewhere
611 # else, as there the permission can be value dependend
612 next if PVE
::QemuServer
::is_valid_drivename
($opt);
613 next if $opt eq 'cdrom';
614 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
615 next if $opt eq 'tags';
618 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
619 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
620 } elsif ($memoryoptions->{$opt}) {
621 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
622 } elsif ($hwtypeoptions->{$opt}) {
623 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
624 } elsif ($generaloptions->{$opt}) {
625 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
626 # special case for startup since it changes host behaviour
627 if ($opt eq 'startup') {
628 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
630 } elsif ($vmpoweroptions->{$opt}) {
631 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
632 } elsif ($diskoptions->{$opt}) {
633 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
634 } elsif ($opt =~ m/^net\d+$/) {
635 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
636 } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\d+$/) {
637 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
638 } elsif ($opt eq 'vmstate') {
639 # the user needs Disk and PowerMgmt privileges to change the vmstate
640 # also needs privileges on the storage, that will be checked later
641 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
643 # catches hostpci\d+, args, lock, etc.
644 # new options will be checked here
645 die "only root can set '$opt' config\n";
652 __PACKAGE__-
>register_method({
656 description
=> "Virtual machine index (per node).",
658 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
662 protected
=> 1, # qemu pid files are only readable by root
664 additionalProperties
=> 0,
666 node
=> get_standard_option
('pve-node'),
670 description
=> "Determine the full status of active VMs.",
678 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
680 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
685 my $rpcenv = PVE
::RPCEnvironment
::get
();
686 my $authuser = $rpcenv->get_user();
688 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
691 foreach my $vmid (keys %$vmstatus) {
692 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
694 my $data = $vmstatus->{$vmid};
701 my $parse_restore_archive = sub {
702 my ($storecfg, $archive) = @_;
704 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
706 if (defined($archive_storeid)) {
707 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
708 if ($scfg->{type
} eq 'pbs') {
715 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
723 __PACKAGE__-
>register_method({
727 description
=> "Create or restore a virtual machine.",
729 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
730 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
731 "If you create disks you need 'Datastore.AllocateSpace' on any used storage." .
732 "If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.",
733 user
=> 'all', # check inside
738 additionalProperties
=> 0,
739 properties
=> PVE
::QemuServer
::json_config_properties
(
741 node
=> get_standard_option
('pve-node'),
742 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
744 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.",
748 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
750 storage
=> get_standard_option
('pve-storage-id', {
751 description
=> "Default storage.",
753 completion
=> \
&PVE
::QemuServer
::complete_storage
,
758 description
=> "Allow to overwrite existing VM.",
759 requires
=> 'archive',
764 description
=> "Assign a unique random ethernet address.",
765 requires
=> 'archive',
770 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
771 requires
=> 'archive',
775 type
=> 'string', format
=> 'pve-poolid',
776 description
=> "Add the VM to the specified pool.",
779 description
=> "Override I/O bandwidth limit (in KiB/s).",
783 default => 'restore limit from datacenter or storage config',
789 description
=> "Start VM after it was created successfully.",
801 my $rpcenv = PVE
::RPCEnvironment
::get
();
802 my $authuser = $rpcenv->get_user();
804 my $node = extract_param
($param, 'node');
805 my $vmid = extract_param
($param, 'vmid');
807 my $archive = extract_param
($param, 'archive');
808 my $is_restore = !!$archive;
810 my $bwlimit = extract_param
($param, 'bwlimit');
811 my $force = extract_param
($param, 'force');
812 my $pool = extract_param
($param, 'pool');
813 my $start_after_create = extract_param
($param, 'start');
814 my $storage = extract_param
($param, 'storage');
815 my $unique = extract_param
($param, 'unique');
816 my $live_restore = extract_param
($param, 'live-restore');
818 if (defined(my $ssh_keys = $param->{sshkeys
})) {
819 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
820 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
823 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
824 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
826 PVE
::Cluster
::check_cfs_quorum
();
828 my $filename = PVE
::QemuConfig-
>config_file($vmid);
829 my $storecfg = PVE
::Storage
::config
();
831 if (defined($pool)) {
832 $rpcenv->check_pool_exist($pool);
835 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
836 if defined($storage);
838 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
840 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
842 } elsif ($archive && $force && (-f
$filename) &&
843 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
844 # OK: user has VM.Backup permissions and wants to restore an existing VM
850 for my $opt (sort keys $param->%*) {
851 if (PVE
::QemuServer
::Drive
::is_valid_drivename
($opt)) {
852 raise_param_exc
({ $opt => "option conflicts with option 'archive'" });
856 if ($archive eq '-') {
857 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
858 $archive = { type
=> 'pipe' };
860 PVE
::Storage
::check_volume_access
(
869 $archive = $parse_restore_archive->($storecfg, $archive);
873 if (scalar(keys $param->%*) > 0) {
874 &$resolve_cdrom_alias($param);
876 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
878 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
880 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
881 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
882 PVE
::QemuServer
::check_bridge_access-
>($rpcenv, $authuser, $param);
883 &$check_cpu_model_access($rpcenv, $authuser, $param);
885 $check_drive_param->($param, $storecfg);
887 PVE
::QemuServer
::add_random_macs
($param);
890 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
892 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
893 die "$emsg $@" if $@;
895 my $restored_data = 0;
896 my $restorefn = sub {
897 my $conf = PVE
::QemuConfig-
>load_config($vmid);
899 PVE
::QemuConfig-
>check_protection($conf, $emsg);
901 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
904 my $restore_options = {
909 live
=> $live_restore,
910 override_conf
=> $param,
912 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
913 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
915 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
916 } elsif ($archive->{type
} eq 'pbs') {
917 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
919 die "unknown backup archive type\n";
923 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
924 # Convert restored VM to template if backup was VM template
925 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
926 warn "Convert to template.\n";
927 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
932 # ensure no old replication state are exists
933 PVE
::ReplicationState
::delete_guest_states
($vmid);
935 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
937 if ($start_after_create && !$live_restore) {
938 print "Execute autostart\n";
939 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
945 # ensure no old replication state are exists
946 PVE
::ReplicationState
::delete_guest_states
($vmid);
950 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
952 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
956 ($vollist, my $created_opts) = $create_disks->(
967 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
969 if (!$conf->{boot
}) {
970 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
971 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
974 # auto generate uuid if user did not specify smbios1 option
975 if (!$conf->{smbios1
}) {
976 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
979 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
980 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
983 my $machine = $conf->{machine
};
984 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
985 # always pin Windows' machine version on create, they get to easily confused
986 if (PVE
::QemuServer
::Helpers
::windows_version
($conf->{ostype
})) {
987 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
991 PVE
::QemuConfig-
>write_config($vmid, $conf);
997 foreach my $volid (@$vollist) {
998 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1004 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
1007 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
1009 if ($start_after_create) {
1010 print "Execute autostart\n";
1011 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
1016 my ($code, $worker_name);
1018 $worker_name = 'qmrestore';
1020 eval { $restorefn->() };
1022 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
1024 if ($restored_data) {
1025 warn "error after data was restored, VM disks should be OK but config may "
1026 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
1028 warn "error before or during data restore, some or all disks were not "
1029 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1035 $worker_name = 'qmcreate';
1037 eval { $createfn->() };
1040 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1041 unlink($conffile) or die "failed to remove config file: $!\n";
1049 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1052 __PACKAGE__-
>register_method({
1057 description
=> "Directory index",
1062 additionalProperties
=> 0,
1064 node
=> get_standard_option
('pve-node'),
1065 vmid
=> get_standard_option
('pve-vmid'),
1073 subdir
=> { type
=> 'string' },
1076 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1082 { subdir
=> 'config' },
1083 { subdir
=> 'cloudinit' },
1084 { subdir
=> 'pending' },
1085 { subdir
=> 'status' },
1086 { subdir
=> 'unlink' },
1087 { subdir
=> 'vncproxy' },
1088 { subdir
=> 'termproxy' },
1089 { subdir
=> 'migrate' },
1090 { subdir
=> 'resize' },
1091 { subdir
=> 'move' },
1092 { subdir
=> 'rrd' },
1093 { subdir
=> 'rrddata' },
1094 { subdir
=> 'monitor' },
1095 { subdir
=> 'agent' },
1096 { subdir
=> 'snapshot' },
1097 { subdir
=> 'spiceproxy' },
1098 { subdir
=> 'sendkey' },
1099 { subdir
=> 'firewall' },
1100 { subdir
=> 'mtunnel' },
1101 { subdir
=> 'remote_migrate' },
1107 __PACKAGE__-
>register_method ({
1108 subclass
=> "PVE::API2::Firewall::VM",
1109 path
=> '{vmid}/firewall',
1112 __PACKAGE__-
>register_method ({
1113 subclass
=> "PVE::API2::Qemu::Agent",
1114 path
=> '{vmid}/agent',
1117 __PACKAGE__-
>register_method({
1119 path
=> '{vmid}/rrd',
1121 protected
=> 1, # fixme: can we avoid that?
1123 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1125 description
=> "Read VM RRD statistics (returns PNG)",
1127 additionalProperties
=> 0,
1129 node
=> get_standard_option
('pve-node'),
1130 vmid
=> get_standard_option
('pve-vmid'),
1132 description
=> "Specify the time frame you are interested in.",
1134 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1137 description
=> "The list of datasources you want to display.",
1138 type
=> 'string', format
=> 'pve-configid-list',
1141 description
=> "The RRD consolidation function",
1143 enum
=> [ 'AVERAGE', 'MAX' ],
1151 filename
=> { type
=> 'string' },
1157 return PVE
::RRD
::create_rrd_graph
(
1158 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1159 $param->{ds
}, $param->{cf
});
1163 __PACKAGE__-
>register_method({
1165 path
=> '{vmid}/rrddata',
1167 protected
=> 1, # fixme: can we avoid that?
1169 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1171 description
=> "Read VM RRD statistics",
1173 additionalProperties
=> 0,
1175 node
=> get_standard_option
('pve-node'),
1176 vmid
=> get_standard_option
('pve-vmid'),
1178 description
=> "Specify the time frame you are interested in.",
1180 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1183 description
=> "The RRD consolidation function",
1185 enum
=> [ 'AVERAGE', 'MAX' ],
1200 return PVE
::RRD
::create_rrd_data
(
1201 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1205 __PACKAGE__-
>register_method({
1206 name
=> 'vm_config',
1207 path
=> '{vmid}/config',
1210 description
=> "Get the virtual machine configuration with pending configuration " .
1211 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1213 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1216 additionalProperties
=> 0,
1218 node
=> get_standard_option
('pve-node'),
1219 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1221 description
=> "Get current values (instead of pending values).",
1226 snapshot
=> get_standard_option
('pve-snapshot-name', {
1227 description
=> "Fetch config values from given snapshot.",
1230 my ($cmd, $pname, $cur, $args) = @_;
1231 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1237 description
=> "The VM configuration.",
1239 properties
=> PVE
::QemuServer
::json_config_properties
({
1242 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1249 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1250 current
=> "cannot use 'snapshot' parameter with 'current'"})
1251 if ($param->{snapshot
} && $param->{current
});
1254 if ($param->{snapshot
}) {
1255 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1257 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1259 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1264 __PACKAGE__-
>register_method({
1265 name
=> 'vm_pending',
1266 path
=> '{vmid}/pending',
1269 description
=> "Get the virtual machine configuration with both current and pending values.",
1271 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1274 additionalProperties
=> 0,
1276 node
=> get_standard_option
('pve-node'),
1277 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1286 description
=> "Configuration option name.",
1290 description
=> "Current value.",
1295 description
=> "Pending value.",
1300 description
=> "Indicates a pending delete request if present and not 0. " .
1301 "The value 2 indicates a force-delete request.",
1313 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1315 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1317 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1318 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1320 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1323 __PACKAGE__-
>register_method({
1324 name
=> 'cloudinit_pending',
1325 path
=> '{vmid}/cloudinit',
1328 description
=> "Get the cloudinit configuration with both current and pending values.",
1330 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1333 additionalProperties
=> 0,
1335 node
=> get_standard_option
('pve-node'),
1336 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1345 description
=> "Configuration option name.",
1349 description
=> "Value as it was used to generate the current cloudinit image.",
1354 description
=> "The new pending value.",
1359 description
=> "Indicates a pending delete request if present and not 0. ",
1371 my $vmid = $param->{vmid
};
1372 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1374 my $ci = $conf->{cloudinit
};
1376 $conf->{cipassword
} = '**********' if exists $conf->{cipassword
};
1377 $ci->{cipassword
} = '**********' if exists $ci->{cipassword
};
1381 # All the values that got added
1382 my $added = delete($ci->{added
}) // '';
1383 for my $key (PVE
::Tools
::split_list
($added)) {
1384 push @$res, { key
=> $key, pending
=> $conf->{$key} };
1387 # All already existing values (+ their new value, if it exists)
1388 for my $opt (keys %$cloudinitoptions) {
1389 next if !$conf->{$opt};
1390 next if $added =~ m/$opt/;
1395 if (my $pending = $ci->{$opt}) {
1396 $item->{value
} = $pending;
1397 $item->{pending
} = $conf->{$opt};
1399 $item->{value
} = $conf->{$opt},
1405 # Now, we'll find the deleted ones
1406 for my $opt (keys %$ci) {
1407 next if $conf->{$opt};
1408 push @$res, { key
=> $opt, delete => 1 };
1414 __PACKAGE__-
>register_method({
1415 name
=> 'cloudinit_update',
1416 path
=> '{vmid}/cloudinit',
1420 description
=> "Regenerate and change cloudinit config drive.",
1422 check
=> ['perm', '/vms/{vmid}', 'VM.Config.Cloudinit'],
1425 additionalProperties
=> 0,
1427 node
=> get_standard_option
('pve-node'),
1428 vmid
=> get_standard_option
('pve-vmid'),
1431 returns
=> { type
=> 'null' },
1435 my $rpcenv = PVE
::RPCEnvironment
::get
();
1436 my $authuser = $rpcenv->get_user();
1438 my $vmid = extract_param
($param, 'vmid');
1440 PVE
::QemuConfig-
>lock_config($vmid, sub {
1441 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1442 PVE
::QemuConfig-
>check_lock($conf);
1444 my $storecfg = PVE
::Storage
::config
();
1445 PVE
::QemuServer
::vmconfig_update_cloudinit_drive
($storecfg, $conf, $vmid);
1450 # POST/PUT {vmid}/config implementation
1452 # The original API used PUT (idempotent) an we assumed that all operations
1453 # are fast. But it turned out that almost any configuration change can
1454 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1455 # time to complete and have side effects (not idempotent).
1457 # The new implementation uses POST and forks a worker process. We added
1458 # a new option 'background_delay'. If specified we wait up to
1459 # 'background_delay' second for the worker task to complete. It returns null
1460 # if the task is finished within that time, else we return the UPID.
1462 my $update_vm_api = sub {
1463 my ($param, $sync) = @_;
1465 my $rpcenv = PVE
::RPCEnvironment
::get
();
1467 my $authuser = $rpcenv->get_user();
1469 my $node = extract_param
($param, 'node');
1471 my $vmid = extract_param
($param, 'vmid');
1473 my $digest = extract_param
($param, 'digest');
1475 my $background_delay = extract_param
($param, 'background_delay');
1477 my $skip_cloud_init = extract_param
($param, 'skip_cloud_init');
1479 if (defined(my $cipassword = $param->{cipassword
})) {
1480 # Same logic as in cloud-init (but with the regex fixed...)
1481 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1482 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1485 my @paramarr = (); # used for log message
1486 foreach my $key (sort keys %$param) {
1487 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1488 push @paramarr, "-$key", $value;
1491 my $skiplock = extract_param
($param, 'skiplock');
1492 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1493 if $skiplock && $authuser ne 'root@pam';
1495 my $delete_str = extract_param
($param, 'delete');
1497 my $revert_str = extract_param
($param, 'revert');
1499 my $force = extract_param
($param, 'force');
1501 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1502 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1503 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1506 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
1507 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
1509 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1511 my $storecfg = PVE
::Storage
::config
();
1513 my $defaults = PVE
::QemuServer
::load_defaults
();
1515 &$resolve_cdrom_alias($param);
1517 # now try to verify all parameters
1520 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1521 if (!PVE
::QemuServer
::option_exists
($opt)) {
1522 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1525 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1526 "-revert $opt' at the same time" })
1527 if defined($param->{$opt});
1529 $revert->{$opt} = 1;
1533 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1534 $opt = 'ide2' if $opt eq 'cdrom';
1536 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1537 "-delete $opt' at the same time" })
1538 if defined($param->{$opt});
1540 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1541 "-revert $opt' at the same time" })
1544 if (!PVE
::QemuServer
::option_exists
($opt)) {
1545 raise_param_exc
({ delete => "unknown option '$opt'" });
1551 my $repl_conf = PVE
::ReplicationConfig-
>new();
1552 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1553 my $check_replication = sub {
1555 return if !$is_replicated;
1556 my $volid = $drive->{file
};
1557 return if !$volid || !($drive->{replicate
}//1);
1558 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1560 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1561 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1562 if !defined($storeid);
1564 return if defined($volname) && $volname eq 'cloudinit';
1567 if ($volid =~ $NEW_DISK_RE) {
1569 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1571 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1573 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1574 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1575 return if $scfg->{shared
};
1576 die "cannot add non-replicatable volume to a replicated VM\n";
1579 $check_drive_param->($param, $storecfg, $check_replication);
1581 foreach my $opt (keys %$param) {
1582 if ($opt =~ m/^net(\d+)$/) {
1584 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1585 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1586 } elsif ($opt eq 'vmgenid') {
1587 if ($param->{$opt} eq '1') {
1588 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1590 } elsif ($opt eq 'hookscript') {
1591 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1592 raise_param_exc
({ $opt => $@ }) if $@;
1596 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1598 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1600 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1602 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $param);
1604 my $updatefn = sub {
1606 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1608 die "checksum missmatch (file change by other user?)\n"
1609 if $digest && $digest ne $conf->{digest
};
1611 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1613 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1614 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1615 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1616 delete $conf->{lock}; # for check lock check, not written out
1617 push @delete, 'lock'; # this is the real deal to write it out
1619 push @delete, 'runningmachine' if $conf->{runningmachine
};
1620 push @delete, 'runningcpu' if $conf->{runningcpu
};
1623 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1625 foreach my $opt (keys %$revert) {
1626 if (defined($conf->{$opt})) {
1627 $param->{$opt} = $conf->{$opt};
1628 } elsif (defined($conf->{pending
}->{$opt})) {
1633 if ($param->{memory
} || defined($param->{balloon
})) {
1634 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1635 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1637 die "balloon value too large (must be smaller than assigned memory)\n"
1638 if $balloon && $balloon > $maxmem;
1641 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1645 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1647 # write updates to pending section
1649 my $modified = {}; # record what $option we modify
1652 if (my $boot = $conf->{boot
}) {
1653 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1654 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1656 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1658 my $check_drive_perms = sub {
1659 my ($opt, $val) = @_;
1660 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1661 if (PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
1662 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Cloudinit', 'VM.Config.CDROM']);
1663 } elsif (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) { # CDROM
1664 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1666 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1671 foreach my $opt (@delete) {
1672 $modified->{$opt} = 1;
1673 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1675 # value of what we want to delete, independent if pending or not
1676 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1677 if (!defined($val)) {
1678 warn "cannot delete '$opt' - not set in current configuration!\n";
1679 $modified->{$opt} = 0;
1682 my $is_pending_val = defined($conf->{pending
}->{$opt});
1683 delete $conf->{pending
}->{$opt};
1685 # remove from bootorder if necessary
1686 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1687 @bootorder = grep {$_ ne $opt} @bootorder;
1688 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1689 $modified->{boot
} = 1;
1692 if ($opt =~ m/^unused/) {
1693 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1694 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1695 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1696 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1697 delete $conf->{$opt};
1698 PVE
::QemuConfig-
>write_config($vmid, $conf);
1700 } elsif ($opt eq 'vmstate') {
1701 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1702 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1703 delete $conf->{$opt};
1704 PVE
::QemuConfig-
>write_config($vmid, $conf);
1706 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1707 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1708 $check_drive_perms->($opt, $val);
1709 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1711 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1712 PVE
::QemuConfig-
>write_config($vmid, $conf);
1713 } elsif ($opt =~ m/^serial\d+$/) {
1714 if ($val eq 'socket') {
1715 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1716 } elsif ($authuser ne 'root@pam') {
1717 die "only root can delete '$opt' config for real devices\n";
1719 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1720 PVE
::QemuConfig-
>write_config($vmid, $conf);
1721 } elsif ($opt =~ m/^usb\d+$/) {
1722 if ($val =~ m/spice/) {
1723 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1724 } elsif ($authuser ne 'root@pam') {
1725 die "only root can delete '$opt' config for real devices\n";
1727 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1728 PVE
::QemuConfig-
>write_config($vmid, $conf);
1729 } elsif ($opt eq 'tags') {
1730 assert_tag_permissions
($vmid, $val, '', $rpcenv, $authuser);
1731 delete $conf->{$opt};
1732 PVE
::QemuConfig-
>write_config($vmid, $conf);
1734 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1735 PVE
::QemuConfig-
>write_config($vmid, $conf);
1739 foreach my $opt (keys %$param) { # add/change
1740 $modified->{$opt} = 1;
1741 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1742 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1744 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1746 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1748 if ($conf->{$opt}) {
1749 $check_drive_perms->($opt, $conf->{$opt});
1753 $check_drive_perms->($opt, $param->{$opt});
1754 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1755 if defined($conf->{pending
}->{$opt});
1757 my (undef, $created_opts) = $create_disks->(
1765 {$opt => $param->{$opt}},
1767 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1769 # default legacy boot order implies all cdroms anyway
1771 # append new CD drives to bootorder to mark them bootable
1772 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1773 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1774 push @bootorder, $opt;
1775 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1776 $modified->{boot
} = 1;
1779 } elsif ($opt =~ m/^serial\d+/) {
1780 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1781 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1782 } elsif ($authuser ne 'root@pam') {
1783 die "only root can modify '$opt' config for real devices\n";
1785 $conf->{pending
}->{$opt} = $param->{$opt};
1786 } elsif ($opt =~ m/^usb\d+/) {
1787 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1788 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1789 } elsif ($authuser ne 'root@pam') {
1790 die "only root can modify '$opt' config for real devices\n";
1792 $conf->{pending
}->{$opt} = $param->{$opt};
1793 } elsif ($opt eq 'tags') {
1794 assert_tag_permissions
($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
1795 $conf->{pending
}->{$opt} = PVE
::GuestHelpers
::get_unique_tags
($param->{$opt});
1797 $conf->{pending
}->{$opt} = $param->{$opt};
1799 if ($opt eq 'boot') {
1800 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1801 if ($new_bootcfg->{order
}) {
1802 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1803 for my $dev (@devs) {
1804 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1805 my $deleted = grep {$_ eq $dev} @delete;
1806 die "invalid bootorder: device '$dev' does not exist'\n"
1807 if !$exists || $deleted;
1810 # remove legacy boot order settings if new one set
1811 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1812 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1813 if $conf->{bootdisk
};
1817 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1818 PVE
::QemuConfig-
>write_config($vmid, $conf);
1821 # remove pending changes when nothing changed
1822 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1823 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1824 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1826 return if !scalar(keys %{$conf->{pending
}});
1828 my $running = PVE
::QemuServer
::check_running
($vmid);
1830 # apply pending changes
1832 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1836 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1838 # cloud_init must be skipped if we are in an incoming, remote live migration
1839 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors, $skip_cloud_init);
1841 raise_param_exc
($errors) if scalar(keys %$errors);
1850 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1852 if ($background_delay) {
1854 # Note: It would be better to do that in the Event based HTTPServer
1855 # to avoid blocking call to sleep.
1857 my $end_time = time() + $background_delay;
1859 my $task = PVE
::Tools
::upid_decode
($upid);
1862 while (time() < $end_time) {
1863 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1865 sleep(1); # this gets interrupted when child process ends
1869 my $status = PVE
::Tools
::upid_read_status
($upid);
1870 return if !PVE
::Tools
::upid_status_is_error
($status);
1871 die "failed to update VM $vmid: $status\n";
1879 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1882 my $vm_config_perm_list = [
1887 'VM.Config.Network',
1889 'VM.Config.Options',
1890 'VM.Config.Cloudinit',
1893 __PACKAGE__-
>register_method({
1894 name
=> 'update_vm_async',
1895 path
=> '{vmid}/config',
1899 description
=> "Set virtual machine options (asynchrounous API).",
1901 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1904 additionalProperties
=> 0,
1905 properties
=> PVE
::QemuServer
::json_config_properties
(
1907 node
=> get_standard_option
('pve-node'),
1908 vmid
=> get_standard_option
('pve-vmid'),
1909 skiplock
=> get_standard_option
('skiplock'),
1911 type
=> 'string', format
=> 'pve-configid-list',
1912 description
=> "A list of settings you want to delete.",
1916 type
=> 'string', format
=> 'pve-configid-list',
1917 description
=> "Revert a pending change.",
1922 description
=> $opt_force_description,
1924 requires
=> 'delete',
1928 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1932 background_delay
=> {
1934 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1940 1, # with_disk_alloc
1947 code
=> $update_vm_api,
1950 __PACKAGE__-
>register_method({
1951 name
=> 'update_vm',
1952 path
=> '{vmid}/config',
1956 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1958 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1961 additionalProperties
=> 0,
1962 properties
=> PVE
::QemuServer
::json_config_properties
(
1964 node
=> get_standard_option
('pve-node'),
1965 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1966 skiplock
=> get_standard_option
('skiplock'),
1968 type
=> 'string', format
=> 'pve-configid-list',
1969 description
=> "A list of settings you want to delete.",
1973 type
=> 'string', format
=> 'pve-configid-list',
1974 description
=> "Revert a pending change.",
1979 description
=> $opt_force_description,
1981 requires
=> 'delete',
1985 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1990 1, # with_disk_alloc
1993 returns
=> { type
=> 'null' },
1996 &$update_vm_api($param, 1);
2001 __PACKAGE__-
>register_method({
2002 name
=> 'destroy_vm',
2007 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
2008 ." and firewall rules",
2010 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2013 additionalProperties
=> 0,
2015 node
=> get_standard_option
('pve-node'),
2016 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2017 skiplock
=> get_standard_option
('skiplock'),
2020 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
2023 'destroy-unreferenced-disks' => {
2025 description
=> "If set, destroy additionally all disks not referenced in the config"
2026 ." but with a matching VMID from all enabled storages.",
2038 my $rpcenv = PVE
::RPCEnvironment
::get
();
2039 my $authuser = $rpcenv->get_user();
2040 my $vmid = $param->{vmid
};
2042 my $skiplock = $param->{skiplock
};
2043 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2044 if $skiplock && $authuser ne 'root@pam';
2046 my $early_checks = sub {
2048 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2049 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
2051 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
2053 if (!$param->{purge
}) {
2054 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
2056 # don't allow destroy if with replication jobs but no purge param
2057 my $repl_conf = PVE
::ReplicationConfig-
>new();
2058 $repl_conf->check_for_existing_jobs($vmid);
2061 die "VM $vmid is running - destroy failed\n"
2062 if PVE
::QemuServer
::check_running
($vmid);
2072 my $storecfg = PVE
::Storage
::config
();
2074 syslog
('info', "destroy VM $vmid: $upid\n");
2075 PVE
::QemuConfig-
>lock_config($vmid, sub {
2076 # repeat, config might have changed
2077 my $ha_managed = $early_checks->();
2079 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
2081 PVE
::QemuServer
::destroy_vm
(
2084 $skiplock, { lock => 'destroyed' },
2085 $purge_unreferenced,
2088 PVE
::AccessControl
::remove_vm_access
($vmid);
2089 PVE
::Firewall
::remove_vmfw_conf
($vmid);
2090 if ($param->{purge
}) {
2091 print "purging VM $vmid from related configurations..\n";
2092 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
2093 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
2096 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
2097 print "NOTE: removed VM $vmid from HA resource configuration.\n";
2101 # only now remove the zombie config, else we can have reuse race
2102 PVE
::QemuConfig-
>destroy_config($vmid);
2106 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
2109 __PACKAGE__-
>register_method({
2111 path
=> '{vmid}/unlink',
2115 description
=> "Unlink/delete disk images.",
2117 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
2120 additionalProperties
=> 0,
2122 node
=> get_standard_option
('pve-node'),
2123 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2125 type
=> 'string', format
=> 'pve-configid-list',
2126 description
=> "A list of disk IDs you want to delete.",
2130 description
=> $opt_force_description,
2135 returns
=> { type
=> 'null'},
2139 $param->{delete} = extract_param
($param, 'idlist');
2141 __PACKAGE__-
>update_vm($param);
2146 # uses good entropy, each char is limited to 6 bit to get printable chars simply
2147 my $gen_rand_chars = sub {
2150 die "invalid length $length" if $length < 1;
2152 my $min = ord('!'); # first printable ascii
2154 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
2155 die "failed to generate random bytes!\n"
2158 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
2165 __PACKAGE__-
>register_method({
2167 path
=> '{vmid}/vncproxy',
2171 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2173 description
=> "Creates a TCP VNC proxy connections.",
2175 additionalProperties
=> 0,
2177 node
=> get_standard_option
('pve-node'),
2178 vmid
=> get_standard_option
('pve-vmid'),
2182 description
=> "starts websockify instead of vncproxy",
2184 'generate-password' => {
2188 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2193 additionalProperties
=> 0,
2195 user
=> { type
=> 'string' },
2196 ticket
=> { type
=> 'string' },
2199 description
=> "Returned if requested with 'generate-password' param."
2200 ." Consists of printable ASCII characters ('!' .. '~').",
2203 cert
=> { type
=> 'string' },
2204 port
=> { type
=> 'integer' },
2205 upid
=> { type
=> 'string' },
2211 my $rpcenv = PVE
::RPCEnvironment
::get
();
2213 my $authuser = $rpcenv->get_user();
2215 my $vmid = $param->{vmid
};
2216 my $node = $param->{node
};
2217 my $websocket = $param->{websocket
};
2219 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2223 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2224 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2227 my $authpath = "/vms/$vmid";
2229 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2230 my $password = $ticket;
2231 if ($param->{'generate-password'}) {
2232 $password = $gen_rand_chars->(8);
2235 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2241 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2242 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2243 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2244 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2245 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2247 $family = PVE
::Tools
::get_host_address_family
($node);
2250 my $port = PVE
::Tools
::next_vnc_port
($family);
2257 syslog
('info', "starting vnc proxy $upid\n");
2261 if (defined($serial)) {
2263 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2265 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2266 '-timeout', $timeout, '-authpath', $authpath,
2267 '-perm', 'Sys.Console'];
2269 if ($param->{websocket
}) {
2270 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2271 push @$cmd, '-notls', '-listen', 'localhost';
2274 push @$cmd, '-c', @$remcmd, @$termcmd;
2276 PVE
::Tools
::run_command
($cmd);
2280 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
2282 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2284 my $sock = IO
::Socket
::IP-
>new(
2289 GetAddrInfoFlags
=> 0,
2290 ) or die "failed to create socket: $!\n";
2291 # Inside the worker we shouldn't have any previous alarms
2292 # running anyway...:
2294 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2296 accept(my $cli, $sock) or die "connection failed: $!\n";
2299 if (PVE
::Tools
::run_command
($cmd,
2300 output
=> '>&'.fileno($cli),
2301 input
=> '<&'.fileno($cli),
2304 die "Failed to run vncproxy.\n";
2311 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2313 PVE
::Tools
::wait_for_vnc_port
($port);
2322 $res->{password
} = $password if $param->{'generate-password'};
2327 __PACKAGE__-
>register_method({
2328 name
=> 'termproxy',
2329 path
=> '{vmid}/termproxy',
2333 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2335 description
=> "Creates a TCP proxy connections.",
2337 additionalProperties
=> 0,
2339 node
=> get_standard_option
('pve-node'),
2340 vmid
=> get_standard_option
('pve-vmid'),
2344 enum
=> [qw(serial0 serial1 serial2 serial3)],
2345 description
=> "opens a serial terminal (defaults to display)",
2350 additionalProperties
=> 0,
2352 user
=> { type
=> 'string' },
2353 ticket
=> { type
=> 'string' },
2354 port
=> { type
=> 'integer' },
2355 upid
=> { type
=> 'string' },
2361 my $rpcenv = PVE
::RPCEnvironment
::get
();
2363 my $authuser = $rpcenv->get_user();
2365 my $vmid = $param->{vmid
};
2366 my $node = $param->{node
};
2367 my $serial = $param->{serial
};
2369 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2371 if (!defined($serial)) {
2373 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2374 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2378 my $authpath = "/vms/$vmid";
2380 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2385 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2386 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2387 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2388 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2389 push @$remcmd, '--';
2391 $family = PVE
::Tools
::get_host_address_family
($node);
2394 my $port = PVE
::Tools
::next_vnc_port
($family);
2396 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2397 push @$termcmd, '-iface', $serial if $serial;
2402 syslog
('info', "starting qemu termproxy $upid\n");
2404 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2405 '--perm', 'VM.Console', '--'];
2406 push @$cmd, @$remcmd, @$termcmd;
2408 PVE
::Tools
::run_command
($cmd);
2411 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2413 PVE
::Tools
::wait_for_vnc_port
($port);
2423 __PACKAGE__-
>register_method({
2424 name
=> 'vncwebsocket',
2425 path
=> '{vmid}/vncwebsocket',
2428 description
=> "You also need to pass a valid ticket (vncticket).",
2429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2431 description
=> "Opens a weksocket for VNC traffic.",
2433 additionalProperties
=> 0,
2435 node
=> get_standard_option
('pve-node'),
2436 vmid
=> get_standard_option
('pve-vmid'),
2438 description
=> "Ticket from previous call to vncproxy.",
2443 description
=> "Port number returned by previous vncproxy call.",
2453 port
=> { type
=> 'string' },
2459 my $rpcenv = PVE
::RPCEnvironment
::get
();
2461 my $authuser = $rpcenv->get_user();
2463 my $vmid = $param->{vmid
};
2464 my $node = $param->{node
};
2466 my $authpath = "/vms/$vmid";
2468 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2470 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2472 # Note: VNC ports are acessible from outside, so we do not gain any
2473 # security if we verify that $param->{port} belongs to VM $vmid. This
2474 # check is done by verifying the VNC ticket (inside VNC protocol).
2476 my $port = $param->{port
};
2478 return { port
=> $port };
2481 __PACKAGE__-
>register_method({
2482 name
=> 'spiceproxy',
2483 path
=> '{vmid}/spiceproxy',
2488 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2490 description
=> "Returns a SPICE configuration to connect to the VM.",
2492 additionalProperties
=> 0,
2494 node
=> get_standard_option
('pve-node'),
2495 vmid
=> get_standard_option
('pve-vmid'),
2496 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2499 returns
=> get_standard_option
('remote-viewer-config'),
2503 my $rpcenv = PVE
::RPCEnvironment
::get
();
2505 my $authuser = $rpcenv->get_user();
2507 my $vmid = $param->{vmid
};
2508 my $node = $param->{node
};
2509 my $proxy = $param->{proxy
};
2511 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2512 my $title = "VM $vmid";
2513 $title .= " - ". $conf->{name
} if $conf->{name
};
2515 my $port = PVE
::QemuServer
::spice_port
($vmid);
2517 my ($ticket, undef, $remote_viewer_config) =
2518 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2520 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2521 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2523 return $remote_viewer_config;
2526 __PACKAGE__-
>register_method({
2528 path
=> '{vmid}/status',
2531 description
=> "Directory index",
2536 additionalProperties
=> 0,
2538 node
=> get_standard_option
('pve-node'),
2539 vmid
=> get_standard_option
('pve-vmid'),
2547 subdir
=> { type
=> 'string' },
2550 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2556 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2559 { subdir
=> 'current' },
2560 { subdir
=> 'start' },
2561 { subdir
=> 'stop' },
2562 { subdir
=> 'reset' },
2563 { subdir
=> 'shutdown' },
2564 { subdir
=> 'suspend' },
2565 { subdir
=> 'reboot' },
2571 __PACKAGE__-
>register_method({
2572 name
=> 'vm_status',
2573 path
=> '{vmid}/status/current',
2576 protected
=> 1, # qemu pid files are only readable by root
2577 description
=> "Get virtual machine status.",
2579 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2582 additionalProperties
=> 0,
2584 node
=> get_standard_option
('pve-node'),
2585 vmid
=> get_standard_option
('pve-vmid'),
2591 %$PVE::QemuServer
::vmstatus_return_properties
,
2593 description
=> "HA manager service status.",
2597 description
=> "QEMU VGA configuration supports spice.",
2602 description
=> "QEMU Guest Agent is enabled in config.",
2612 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2614 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2615 my $status = $vmstatus->{$param->{vmid
}};
2617 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2620 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2621 my $spice = defined($vga->{type
}) && $vga->{type
} =~ /^virtio/;
2622 $spice ||= PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2623 $status->{spice
} = 1 if $spice;
2625 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2630 __PACKAGE__-
>register_method({
2632 path
=> '{vmid}/status/start',
2636 description
=> "Start virtual machine.",
2638 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2641 additionalProperties
=> 0,
2643 node
=> get_standard_option
('pve-node'),
2644 vmid
=> get_standard_option
('pve-vmid',
2645 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2646 skiplock
=> get_standard_option
('skiplock'),
2647 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2648 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2651 enum
=> ['secure', 'insecure'],
2652 description
=> "Migration traffic is encrypted using an SSH " .
2653 "tunnel by default. On secure, completely private networks " .
2654 "this can be disabled to increase performance.",
2657 migration_network
=> {
2658 type
=> 'string', format
=> 'CIDR',
2659 description
=> "CIDR of the (sub) network that is used for migration.",
2662 machine
=> get_standard_option
('pve-qemu-machine'),
2664 description
=> "Override QEMU's -cpu argument with the given string.",
2668 targetstorage
=> get_standard_option
('pve-targetstorage'),
2670 description
=> "Wait maximal timeout seconds.",
2673 default => 'max(30, vm memory in GiB)',
2684 my $rpcenv = PVE
::RPCEnvironment
::get
();
2685 my $authuser = $rpcenv->get_user();
2687 my $node = extract_param
($param, 'node');
2688 my $vmid = extract_param
($param, 'vmid');
2689 my $timeout = extract_param
($param, 'timeout');
2690 my $machine = extract_param
($param, 'machine');
2692 my $get_root_param = sub {
2693 my $value = extract_param
($param, $_[0]);
2694 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2695 if $value && $authuser ne 'root@pam';
2699 my $stateuri = $get_root_param->('stateuri');
2700 my $skiplock = $get_root_param->('skiplock');
2701 my $migratedfrom = $get_root_param->('migratedfrom');
2702 my $migration_type = $get_root_param->('migration_type');
2703 my $migration_network = $get_root_param->('migration_network');
2704 my $targetstorage = $get_root_param->('targetstorage');
2705 my $force_cpu = $get_root_param->('force-cpu');
2709 if ($targetstorage) {
2710 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2712 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2713 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2717 # read spice ticket from STDIN
2719 my $nbd_protocol_version = 0;
2720 my $replicated_volumes = {};
2721 my $offline_volumes = {};
2722 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2723 while (defined(my $line = <STDIN
>)) {
2725 if ($line =~ m/^spice_ticket: (.+)$/) {
2727 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2728 $nbd_protocol_version = $1;
2729 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2730 $replicated_volumes->{$1} = 1;
2731 } elsif ($line =~ m/^tpmstate0: (.*)$/) { # Deprecated, use offline_volume instead
2732 $offline_volumes->{tpmstate0
} = $1;
2733 } elsif ($line =~ m/^offline_volume: ([^:]+): (.*)$/) {
2734 $offline_volumes->{$1} = $2;
2735 } elsif (!$spice_ticket) {
2736 # fallback for old source node
2737 $spice_ticket = $line;
2739 warn "unknown 'start' parameter on STDIN: '$line'\n";
2744 PVE
::Cluster
::check_cfs_quorum
();
2746 my $storecfg = PVE
::Storage
::config
();
2748 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2752 print "Requesting HA start for VM $vmid\n";
2754 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2755 PVE
::Tools
::run_command
($cmd);
2759 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2766 syslog
('info', "start VM $vmid: $upid\n");
2768 my $migrate_opts = {
2769 migratedfrom
=> $migratedfrom,
2770 spice_ticket
=> $spice_ticket,
2771 network
=> $migration_network,
2772 type
=> $migration_type,
2773 storagemap
=> $storagemap,
2774 nbd_proto_version
=> $nbd_protocol_version,
2775 replicated_volumes
=> $replicated_volumes,
2776 offline_volumes
=> $offline_volumes,
2780 statefile
=> $stateuri,
2781 skiplock
=> $skiplock,
2782 forcemachine
=> $machine,
2783 timeout
=> $timeout,
2784 forcecpu
=> $force_cpu,
2787 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2791 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2795 __PACKAGE__-
>register_method({
2797 path
=> '{vmid}/status/stop',
2801 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2802 "is akin to pulling the power plug of a running computer and may damage the VM data",
2804 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2807 additionalProperties
=> 0,
2809 node
=> get_standard_option
('pve-node'),
2810 vmid
=> get_standard_option
('pve-vmid',
2811 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2812 skiplock
=> get_standard_option
('skiplock'),
2813 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2815 description
=> "Wait maximal timeout seconds.",
2821 description
=> "Do not deactivate storage volumes.",
2834 my $rpcenv = PVE
::RPCEnvironment
::get
();
2835 my $authuser = $rpcenv->get_user();
2837 my $node = extract_param
($param, 'node');
2838 my $vmid = extract_param
($param, 'vmid');
2840 my $skiplock = extract_param
($param, 'skiplock');
2841 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2842 if $skiplock && $authuser ne 'root@pam';
2844 my $keepActive = extract_param
($param, 'keepActive');
2845 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2846 if $keepActive && $authuser ne 'root@pam';
2848 my $migratedfrom = extract_param
($param, 'migratedfrom');
2849 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2850 if $migratedfrom && $authuser ne 'root@pam';
2853 my $storecfg = PVE
::Storage
::config
();
2855 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2860 print "Requesting HA stop for VM $vmid\n";
2862 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2863 PVE
::Tools
::run_command
($cmd);
2867 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2873 syslog
('info', "stop VM $vmid: $upid\n");
2875 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2876 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2880 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2884 __PACKAGE__-
>register_method({
2886 path
=> '{vmid}/status/reset',
2890 description
=> "Reset virtual machine.",
2892 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2895 additionalProperties
=> 0,
2897 node
=> get_standard_option
('pve-node'),
2898 vmid
=> get_standard_option
('pve-vmid',
2899 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2900 skiplock
=> get_standard_option
('skiplock'),
2909 my $rpcenv = PVE
::RPCEnvironment
::get
();
2911 my $authuser = $rpcenv->get_user();
2913 my $node = extract_param
($param, 'node');
2915 my $vmid = extract_param
($param, 'vmid');
2917 my $skiplock = extract_param
($param, 'skiplock');
2918 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2919 if $skiplock && $authuser ne 'root@pam';
2921 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2926 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2931 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2934 __PACKAGE__-
>register_method({
2935 name
=> 'vm_shutdown',
2936 path
=> '{vmid}/status/shutdown',
2940 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2941 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2943 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2946 additionalProperties
=> 0,
2948 node
=> get_standard_option
('pve-node'),
2949 vmid
=> get_standard_option
('pve-vmid',
2950 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2951 skiplock
=> get_standard_option
('skiplock'),
2953 description
=> "Wait maximal timeout seconds.",
2959 description
=> "Make sure the VM stops.",
2965 description
=> "Do not deactivate storage volumes.",
2978 my $rpcenv = PVE
::RPCEnvironment
::get
();
2979 my $authuser = $rpcenv->get_user();
2981 my $node = extract_param
($param, 'node');
2982 my $vmid = extract_param
($param, 'vmid');
2984 my $skiplock = extract_param
($param, 'skiplock');
2985 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2986 if $skiplock && $authuser ne 'root@pam';
2988 my $keepActive = extract_param
($param, 'keepActive');
2989 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2990 if $keepActive && $authuser ne 'root@pam';
2992 my $storecfg = PVE
::Storage
::config
();
2996 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2997 # otherwise, we will infer a shutdown command, but run into the timeout,
2998 # then when the vm is resumed, it will instantly shutdown
3000 # checking the qmp status here to get feedback to the gui/cli/api
3001 # and the status query should not take too long
3002 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
3003 if ($param->{forceStop
}) {
3004 warn "VM is paused - stop instead of shutdown\n";
3007 die "VM is paused - cannot shutdown\n";
3011 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3013 my $timeout = $param->{timeout
} // 60;
3017 print "Requesting HA stop for VM $vmid\n";
3019 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
3020 PVE
::Tools
::run_command
($cmd);
3024 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
3031 syslog
('info', "shutdown VM $vmid: $upid\n");
3033 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
3034 $shutdown, $param->{forceStop
}, $keepActive);
3038 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
3042 __PACKAGE__-
>register_method({
3043 name
=> 'vm_reboot',
3044 path
=> '{vmid}/status/reboot',
3048 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
3050 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3053 additionalProperties
=> 0,
3055 node
=> get_standard_option
('pve-node'),
3056 vmid
=> get_standard_option
('pve-vmid',
3057 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3059 description
=> "Wait maximal timeout seconds for the shutdown.",
3072 my $rpcenv = PVE
::RPCEnvironment
::get
();
3073 my $authuser = $rpcenv->get_user();
3075 my $node = extract_param
($param, 'node');
3076 my $vmid = extract_param
($param, 'vmid');
3078 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
3080 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3085 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
3086 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
3090 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
3093 __PACKAGE__-
>register_method({
3094 name
=> 'vm_suspend',
3095 path
=> '{vmid}/status/suspend',
3099 description
=> "Suspend virtual machine.",
3101 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
3102 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
3103 " on the storage for the vmstate.",
3104 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3107 additionalProperties
=> 0,
3109 node
=> get_standard_option
('pve-node'),
3110 vmid
=> get_standard_option
('pve-vmid',
3111 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3112 skiplock
=> get_standard_option
('skiplock'),
3117 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
3119 statestorage
=> get_standard_option
('pve-storage-id', {
3120 description
=> "The storage for the VM state",
3121 requires
=> 'todisk',
3123 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
3133 my $rpcenv = PVE
::RPCEnvironment
::get
();
3134 my $authuser = $rpcenv->get_user();
3136 my $node = extract_param
($param, 'node');
3137 my $vmid = extract_param
($param, 'vmid');
3139 my $todisk = extract_param
($param, 'todisk') // 0;
3141 my $statestorage = extract_param
($param, 'statestorage');
3143 my $skiplock = extract_param
($param, 'skiplock');
3144 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3145 if $skiplock && $authuser ne 'root@pam';
3147 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3149 die "Cannot suspend HA managed VM to disk\n"
3150 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
3152 # early check for storage permission, for better user feedback
3154 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3155 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3157 # cannot save the state of a non-virtualized PCIe device, so resume cannot really work
3158 for my $key (keys %$conf) {
3159 next if $key !~ /^hostpci\d+/;
3160 die "cannot suspend VM to disk due to passed-through PCI device(s), which lack the"
3161 ." possibility to save/restore their internal state\n";
3164 if (!$statestorage) {
3165 # get statestorage from config if none is given
3166 my $storecfg = PVE
::Storage
::config
();
3167 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
3170 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
3176 syslog
('info', "suspend VM $vmid: $upid\n");
3178 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
3183 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
3184 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
3187 __PACKAGE__-
>register_method({
3188 name
=> 'vm_resume',
3189 path
=> '{vmid}/status/resume',
3193 description
=> "Resume virtual machine.",
3195 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3198 additionalProperties
=> 0,
3200 node
=> get_standard_option
('pve-node'),
3201 vmid
=> get_standard_option
('pve-vmid',
3202 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3203 skiplock
=> get_standard_option
('skiplock'),
3204 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3214 my $rpcenv = PVE
::RPCEnvironment
::get
();
3216 my $authuser = $rpcenv->get_user();
3218 my $node = extract_param
($param, 'node');
3220 my $vmid = extract_param
($param, 'vmid');
3222 my $skiplock = extract_param
($param, 'skiplock');
3223 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3224 if $skiplock && $authuser ne 'root@pam';
3226 # nocheck is used as part of migration when config file might be still
3228 my $nocheck = extract_param
($param, 'nocheck');
3229 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3230 if $nocheck && $authuser ne 'root@pam';
3232 my $to_disk_suspended;
3234 PVE
::QemuConfig-
>lock_config($vmid, sub {
3235 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3236 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3240 die "VM $vmid not running\n"
3241 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3246 syslog
('info', "resume VM $vmid: $upid\n");
3248 if (!$to_disk_suspended) {
3249 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3251 my $storecfg = PVE
::Storage
::config
();
3252 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3258 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3261 __PACKAGE__-
>register_method({
3262 name
=> 'vm_sendkey',
3263 path
=> '{vmid}/sendkey',
3267 description
=> "Send key event to virtual machine.",
3269 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3272 additionalProperties
=> 0,
3274 node
=> get_standard_option
('pve-node'),
3275 vmid
=> get_standard_option
('pve-vmid',
3276 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3277 skiplock
=> get_standard_option
('skiplock'),
3279 description
=> "The key (qemu monitor encoding).",
3284 returns
=> { type
=> 'null'},
3288 my $rpcenv = PVE
::RPCEnvironment
::get
();
3290 my $authuser = $rpcenv->get_user();
3292 my $node = extract_param
($param, 'node');
3294 my $vmid = extract_param
($param, 'vmid');
3296 my $skiplock = extract_param
($param, 'skiplock');
3297 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3298 if $skiplock && $authuser ne 'root@pam';
3300 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3305 __PACKAGE__-
>register_method({
3306 name
=> 'vm_feature',
3307 path
=> '{vmid}/feature',
3311 description
=> "Check if feature for virtual machine is available.",
3313 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3316 additionalProperties
=> 0,
3318 node
=> get_standard_option
('pve-node'),
3319 vmid
=> get_standard_option
('pve-vmid'),
3321 description
=> "Feature to check.",
3323 enum
=> [ 'snapshot', 'clone', 'copy' ],
3325 snapname
=> get_standard_option
('pve-snapshot-name', {
3333 hasFeature
=> { type
=> 'boolean' },
3336 items
=> { type
=> 'string' },
3343 my $node = extract_param
($param, 'node');
3345 my $vmid = extract_param
($param, 'vmid');
3347 my $snapname = extract_param
($param, 'snapname');
3349 my $feature = extract_param
($param, 'feature');
3351 my $running = PVE
::QemuServer
::check_running
($vmid);
3353 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3356 my $snap = $conf->{snapshots
}->{$snapname};
3357 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3360 my $storecfg = PVE
::Storage
::config
();
3362 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3363 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3366 hasFeature
=> $hasFeature,
3367 nodes
=> [ keys %$nodelist ],
3371 __PACKAGE__-
>register_method({
3373 path
=> '{vmid}/clone',
3377 description
=> "Create a copy of virtual machine/template.",
3379 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3380 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3381 "'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet",
3384 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3386 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3387 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3392 additionalProperties
=> 0,
3394 node
=> get_standard_option
('pve-node'),
3395 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3396 newid
=> get_standard_option
('pve-vmid', {
3397 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3398 description
=> 'VMID for the clone.' }),
3401 type
=> 'string', format
=> 'dns-name',
3402 description
=> "Set a name for the new VM.",
3407 description
=> "Description for the new VM.",
3411 type
=> 'string', format
=> 'pve-poolid',
3412 description
=> "Add the new VM to the specified pool.",
3414 snapname
=> get_standard_option
('pve-snapshot-name', {
3417 storage
=> get_standard_option
('pve-storage-id', {
3418 description
=> "Target storage for full clone.",
3422 description
=> "Target format for file storage. Only valid for full clone.",
3425 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3430 description
=> "Create a full copy of all disks. This is always done when " .
3431 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3433 target
=> get_standard_option
('pve-node', {
3434 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3438 description
=> "Override I/O bandwidth limit (in KiB/s).",
3442 default => 'clone limit from datacenter or storage config',
3452 my $rpcenv = PVE
::RPCEnvironment
::get
();
3453 my $authuser = $rpcenv->get_user();
3455 my $node = extract_param
($param, 'node');
3456 my $vmid = extract_param
($param, 'vmid');
3457 my $newid = extract_param
($param, 'newid');
3458 my $pool = extract_param
($param, 'pool');
3460 my $snapname = extract_param
($param, 'snapname');
3461 my $storage = extract_param
($param, 'storage');
3462 my $format = extract_param
($param, 'format');
3463 my $target = extract_param
($param, 'target');
3465 my $localnode = PVE
::INotify
::nodename
();
3467 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3471 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3473 my $load_and_check = sub {
3474 $rpcenv->check_pool_exist($pool) if defined($pool);
3475 PVE
::Cluster
::check_node_exists
($target) if $target;
3477 my $storecfg = PVE
::Storage
::config
();
3480 # check if storage is enabled on local node
3481 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3483 # check if storage is available on target node
3484 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3485 # clone only works if target storage is shared
3486 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3487 die "can't clone to non-shared storage '$storage'\n"
3488 if !$scfg->{shared
};
3492 PVE
::Cluster
::check_cfs_quorum
();
3494 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3495 PVE
::QemuConfig-
>check_lock($conf);
3497 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3498 die "unexpected state change\n" if $verify_running != $running;
3500 die "snapshot '$snapname' does not exist\n"
3501 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3503 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3505 die "parameter 'storage' not allowed for linked clones\n"
3506 if defined($storage) && !$full;
3508 die "parameter 'format' not allowed for linked clones\n"
3509 if defined($format) && !$full;
3511 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3513 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3515 PVE
::QemuServer
::check_bridge_access
($rpcenv, $authuser, $oldconf);
3517 die "can't clone VM to node '$target' (VM uses local storage)\n"
3518 if $target && !$sharedvm;
3520 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3521 die "unable to create VM $newid: config file already exists\n"
3524 my $newconf = { lock => 'clone' };
3529 foreach my $opt (keys %$oldconf) {
3530 my $value = $oldconf->{$opt};
3532 # do not copy snapshot related info
3533 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3534 $opt eq 'vmstate' || $opt eq 'snapstate';
3536 # no need to copy unused images, because VMID(owner) changes anyways
3537 next if $opt =~ m/^unused\d+$/;
3539 die "cannot clone TPM state while VM is running\n"
3540 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3542 # always change MAC! address
3543 if ($opt =~ m/^net(\d+)$/) {
3544 my $net = PVE
::QemuServer
::parse_net
($value);
3545 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3546 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3547 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3548 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3549 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3550 die "unable to parse drive options for '$opt'\n" if !$drive;
3551 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3552 $newconf->{$opt} = $value; # simply copy configuration
3554 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3555 die "Full clone feature is not supported for drive '$opt'\n"
3556 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3557 $fullclone->{$opt} = 1;
3559 # not full means clone instead of copy
3560 die "Linked clone feature is not supported for drive '$opt'\n"
3561 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3563 $drives->{$opt} = $drive;
3564 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3565 push @$vollist, $drive->{file
};
3568 # copy everything else
3569 $newconf->{$opt} = $value;
3573 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3577 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3578 my $storecfg = PVE
::Storage
::config
();
3580 # auto generate a new uuid
3581 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3582 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3583 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3584 # auto generate a new vmgenid only if the option was set for template
3585 if ($newconf->{vmgenid
}) {
3586 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3589 delete $newconf->{template
};
3591 if ($param->{name
}) {
3592 $newconf->{name
} = $param->{name
};
3594 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3597 if ($param->{description
}) {
3598 $newconf->{description
} = $param->{description
};
3601 # create empty/temp config - this fails if VM already exists on other node
3602 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3603 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3605 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3607 my $newvollist = [];
3614 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3616 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3618 my $bwlimit = extract_param
($param, 'bwlimit');
3620 my $total_jobs = scalar(keys %{$drives});
3623 foreach my $opt (sort keys %$drives) {
3624 my $drive = $drives->{$opt};
3625 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3626 my $completion = $skipcomplete ?
'skip' : 'complete';
3628 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3629 my $storage_list = [ $src_sid ];
3630 push @$storage_list, $storage if defined($storage);
3631 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3635 running
=> $running,
3638 snapname
=> $snapname,
3644 storage
=> $storage,
3648 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3649 if $opt eq 'efidisk0';
3651 my $newdrive = PVE
::QemuServer
::clone_disk
(
3663 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3665 PVE
::QemuConfig-
>write_config($newid, $newconf);
3669 delete $newconf->{lock};
3671 # do not write pending changes
3672 if (my @changes = keys %{$newconf->{pending
}}) {
3673 my $pending = join(',', @changes);
3674 warn "found pending changes for '$pending', discarding for clone\n";
3675 delete $newconf->{pending
};
3678 PVE
::QemuConfig-
>write_config($newid, $newconf);
3681 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3682 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3683 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3685 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3686 die "Failed to move config to node '$target' - rename failed: $!\n"
3687 if !rename($conffile, $newconffile);
3690 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3693 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3694 sleep 1; # some storage like rbd need to wait before release volume - really?
3696 foreach my $volid (@$newvollist) {
3697 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3701 PVE
::Firewall
::remove_vmfw_conf
($newid);
3703 unlink $conffile; # avoid races -> last thing before die
3705 die "clone failed: $err";
3711 # Aquire exclusive lock lock for $newid
3712 my $lock_target_vm = sub {
3713 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3716 my $lock_source_vm = sub {
3717 # exclusive lock if VM is running - else shared lock is enough;
3719 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3721 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3725 $load_and_check->(); # early checks before forking/locking
3727 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3730 __PACKAGE__-
>register_method({
3731 name
=> 'move_vm_disk',
3732 path
=> '{vmid}/move_disk',
3736 description
=> "Move volume to different storage or to a different VM.",
3738 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3739 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3740 "a disk to another VM, you need the permissions on the target VM as well.",
3741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3744 additionalProperties
=> 0,
3746 node
=> get_standard_option
('pve-node'),
3747 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3748 'target-vmid' => get_standard_option
('pve-vmid', {
3749 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3754 description
=> "The disk you want to move.",
3755 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3757 storage
=> get_standard_option
('pve-storage-id', {
3758 description
=> "Target storage.",
3759 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3764 description
=> "Target Format.",
3765 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3770 description
=> "Delete the original disk after successful copy. By default the"
3771 ." original disk is kept as unused disk.",
3777 description
=> 'Prevent changes if current configuration file has different SHA1"
3778 ." digest. This can be used to prevent concurrent modifications.',
3783 description
=> "Override I/O bandwidth limit (in KiB/s).",
3787 default => 'move limit from datacenter or storage config',
3791 description
=> "The config key the disk will be moved to on the target VM"
3792 ." (for example, ide0 or scsi1). Default is the source disk key.",
3793 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3796 'target-digest' => {
3798 description
=> 'Prevent changes if the current config file of the target VM has a"
3799 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3807 description
=> "the task ID.",
3812 my $rpcenv = PVE
::RPCEnvironment
::get
();
3813 my $authuser = $rpcenv->get_user();
3815 my $node = extract_param
($param, 'node');
3816 my $vmid = extract_param
($param, 'vmid');
3817 my $target_vmid = extract_param
($param, 'target-vmid');
3818 my $digest = extract_param
($param, 'digest');
3819 my $target_digest = extract_param
($param, 'target-digest');
3820 my $disk = extract_param
($param, 'disk');
3821 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3822 my $storeid = extract_param
($param, 'storage');
3823 my $format = extract_param
($param, 'format');
3825 my $storecfg = PVE
::Storage
::config
();
3827 my $load_and_check_move = sub {
3828 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3829 PVE
::QemuConfig-
>check_lock($conf);
3831 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3833 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3835 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3837 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3838 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3840 my $old_volid = $drive->{file
};
3842 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3843 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3847 die "you can't move to the same storage with same format\n"
3848 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3850 # this only checks snapshots because $disk is passed!
3851 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3857 die "you can't move a disk with snapshots and delete the source\n"
3858 if $snapshotted && $param->{delete};
3860 return ($conf, $drive, $oldstoreid, $snapshotted);
3863 my $move_updatefn = sub {
3864 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3865 my $old_volid = $drive->{file
};
3867 PVE
::Cluster
::log_msg
(
3870 "move disk VM $vmid: move --disk $disk --storage $storeid"
3873 my $running = PVE
::QemuServer
::check_running
($vmid);
3875 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3877 my $newvollist = [];
3883 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3885 warn "moving disk with snapshots, snapshots will not be moved!\n"
3888 my $bwlimit = extract_param
($param, 'bwlimit');
3889 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3891 [$oldstoreid, $storeid],
3897 running
=> $running,
3906 storage
=> $storeid,
3910 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3911 if $disk eq 'efidisk0';
3913 my $newdrive = PVE
::QemuServer
::clone_disk
(
3924 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3926 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3928 # convert moved disk to base if part of template
3929 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3930 if PVE
::QemuConfig-
>is_template($conf);
3932 PVE
::QemuConfig-
>write_config($vmid, $conf);
3934 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3935 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3936 eval { mon_cmd
($vmid, "guest-fstrim") };
3940 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3941 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3947 foreach my $volid (@$newvollist) {
3948 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3951 die "storage migration failed: $err";
3954 if ($param->{delete}) {
3956 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3957 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3963 my $load_and_check_reassign_configs = sub {
3964 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
3966 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
3967 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
3969 my $source_node = $vmlist->{$vmid}->{node
};
3970 my $target_node = $vmlist->{$target_vmid}->{node
};
3972 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
3973 if $source_node ne $target_node;
3975 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
3976 PVE
::QemuConfig-
>check_lock($source_conf);
3977 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
3978 PVE
::QemuConfig-
>check_lock($target_conf);
3980 die "Can't move disks from or to template VMs\n"
3981 if ($source_conf->{template
} || $target_conf->{template
});
3984 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
3985 die "VM ${vmid}: $@" if $@;
3988 if ($target_digest) {
3989 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
3990 die "VM ${target_vmid}: $@" if $@;
3993 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
3995 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
3996 if $target_conf->{$target_disk};
3998 my $drive = PVE
::QemuServer
::parse_drive
(
4000 $source_conf->{$disk},
4002 die "failed to parse source disk - $@\n" if !$drive;
4004 my $source_volid = $drive->{file
};
4006 die "disk '${disk}' has no associated volume\n" if !$source_volid;
4007 die "CD drive contents can't be moved to another VM\n"
4008 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
4010 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
4011 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
4013 die "Can't move disk used by a snapshot to another VM\n"
4014 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
4015 die "Storage does not support moving of this disk to another VM\n"
4016 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
4017 die "Cannot move disk to another VM while the source VM is running - detach first\n"
4018 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
4020 # now re-parse using target disk slot format
4021 if ($target_disk =~ /^unused\d+$/) {
4022 $drive = PVE
::QemuServer
::parse_drive
(
4027 $drive = PVE
::QemuServer
::parse_drive
(
4029 $source_conf->{$disk},
4032 die "failed to parse source disk for target disk format - $@\n" if !$drive;
4034 my $repl_conf = PVE
::ReplicationConfig-
>new();
4035 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
4036 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4037 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
4038 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
4041 return ($source_conf, $target_conf, $drive);
4046 print STDERR
"$msg\n";
4049 my $disk_reassignfn = sub {
4050 return PVE
::QemuConfig-
>lock_config($vmid, sub {
4051 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
4052 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
4054 my $source_volid = $drive->{file
};
4056 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
4057 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
4059 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
4061 my $new_volid = PVE
::Storage
::rename_volume
(
4067 $drive->{file
} = $new_volid;
4069 my $boot_order = PVE
::QemuServer
::device_bootorder
($source_conf);
4070 if (defined(delete $boot_order->{$disk})) {
4071 print "removing disk '$disk' from boot order config\n";
4072 my $boot_devs = [ sort { $boot_order->{$a} <=> $boot_order->{$b} } keys %$boot_order ];
4073 $source_conf->{boot
} = PVE
::QemuServer
::print_bootorder
($boot_devs);
4076 delete $source_conf->{$disk};
4077 print "removing disk '${disk}' from VM '${vmid}' config\n";
4078 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
4080 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
4082 if ($target_disk =~ /^unused\d+$/) {
4083 $target_conf->{$target_disk} = $drive_string;
4084 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
4089 vmid
=> $target_vmid,
4090 digest
=> $target_digest,
4091 $target_disk => $drive_string,
4097 # remove possible replication snapshots
4098 if (PVE
::Storage
::volume_has_feature
(
4104 PVE
::Replication
::prepare
(
4114 print "Failed to remove replication snapshots on moved disk " .
4115 "'$target_disk'. Manual cleanup could be necessary.\n";
4122 if ($target_vmid && $storeid) {
4123 my $msg = "either set 'storage' or 'target-vmid', but not both";
4124 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4125 } elsif ($target_vmid) {
4126 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
4127 if $authuser ne 'root@pam';
4129 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
4130 if $vmid eq $target_vmid;
4132 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
4133 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
4134 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
4136 return $rpcenv->fork_worker(
4138 "${vmid}-${disk}>${target_vmid}-${target_disk}",
4142 } elsif ($storeid) {
4143 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4145 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
4146 if $disk =~ m/^unused\d+$/;
4148 $load_and_check_move->(); # early checks before forking/locking
4151 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
4154 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
4156 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
4157 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
4161 my $check_vm_disks_local = sub {
4162 my ($storecfg, $vmconf, $vmid) = @_;
4164 my $local_disks = {};
4166 # add some more information to the disks e.g. cdrom
4167 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
4168 my ($volid, $attr) = @_;
4170 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
4172 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
4173 return if $scfg->{shared
};
4175 # The shared attr here is just a special case where the vdisk
4176 # is marked as shared manually
4177 return if $attr->{shared
};
4178 return if $attr->{cdrom
} and $volid eq "none";
4180 if (exists $local_disks->{$volid}) {
4181 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
4183 $local_disks->{$volid} = $attr;
4184 # ensure volid is present in case it's needed
4185 $local_disks->{$volid}->{volid
} = $volid;
4189 return $local_disks;
4192 __PACKAGE__-
>register_method({
4193 name
=> 'migrate_vm_precondition',
4194 path
=> '{vmid}/migrate',
4198 description
=> "Get preconditions for migration.",
4200 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4203 additionalProperties
=> 0,
4205 node
=> get_standard_option
('pve-node'),
4206 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4207 target
=> get_standard_option
('pve-node', {
4208 description
=> "Target node.",
4209 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4217 running
=> { type
=> 'boolean' },
4221 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4223 not_allowed_nodes
=> {
4226 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4230 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4232 local_resources
=> {
4234 description
=> "List local resources e.g. pci, usb"
4241 my $rpcenv = PVE
::RPCEnvironment
::get
();
4243 my $authuser = $rpcenv->get_user();
4245 PVE
::Cluster
::check_cfs_quorum
();
4249 my $vmid = extract_param
($param, 'vmid');
4250 my $target = extract_param
($param, 'target');
4251 my $localnode = PVE
::INotify
::nodename
();
4255 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4256 my $storecfg = PVE
::Storage
::config
();
4259 # try to detect errors early
4260 PVE
::QemuConfig-
>check_lock($vmconf);
4262 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4264 # if vm is not running, return target nodes where local storage is available
4265 # for offline migration
4266 if (!$res->{running
}) {
4267 $res->{allowed_nodes
} = [];
4268 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4269 delete $checked_nodes->{$localnode};
4271 foreach my $node (keys %$checked_nodes) {
4272 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
4273 push @{$res->{allowed_nodes
}}, $node;
4277 $res->{not_allowed_nodes
} = $checked_nodes;
4281 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4282 $res->{local_disks
} = [ values %$local_disks ];;
4284 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4286 $res->{local_resources
} = $local_resources;
4293 __PACKAGE__-
>register_method({
4294 name
=> 'migrate_vm',
4295 path
=> '{vmid}/migrate',
4299 description
=> "Migrate virtual machine. Creates a new migration task.",
4301 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4304 additionalProperties
=> 0,
4306 node
=> get_standard_option
('pve-node'),
4307 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4308 target
=> get_standard_option
('pve-node', {
4309 description
=> "Target node.",
4310 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4314 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4319 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4324 enum
=> ['secure', 'insecure'],
4325 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4328 migration_network
=> {
4329 type
=> 'string', format
=> 'CIDR',
4330 description
=> "CIDR of the (sub) network that is used for migration.",
4333 "with-local-disks" => {
4335 description
=> "Enable live storage migration for local disk",
4338 targetstorage
=> get_standard_option
('pve-targetstorage', {
4339 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4342 description
=> "Override I/O bandwidth limit (in KiB/s).",
4346 default => 'migrate limit from datacenter or storage config',
4352 description
=> "the task ID.",
4357 my $rpcenv = PVE
::RPCEnvironment
::get
();
4358 my $authuser = $rpcenv->get_user();
4360 my $target = extract_param
($param, 'target');
4362 my $localnode = PVE
::INotify
::nodename
();
4363 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4365 PVE
::Cluster
::check_cfs_quorum
();
4367 PVE
::Cluster
::check_node_exists
($target);
4369 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4371 my $vmid = extract_param
($param, 'vmid');
4373 raise_param_exc
({ force
=> "Only root may use this option." })
4374 if $param->{force
} && $authuser ne 'root@pam';
4376 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4377 if $param->{migration_type
} && $authuser ne 'root@pam';
4379 # allow root only until better network permissions are available
4380 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4381 if $param->{migration_network
} && $authuser ne 'root@pam';
4384 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4386 # try to detect errors early
4388 PVE
::QemuConfig-
>check_lock($conf);
4390 if (PVE
::QemuServer
::check_running
($vmid)) {
4391 die "can't migrate running VM without --online\n" if !$param->{online
};
4393 my $repl_conf = PVE
::ReplicationConfig-
>new();
4394 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4395 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4396 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4397 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4398 "target. Use 'force' to override.\n";
4401 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4402 $param->{online
} = 0;
4405 my $storecfg = PVE
::Storage
::config
();
4406 if (my $targetstorage = $param->{targetstorage
}) {
4407 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4408 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4411 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4412 if !defined($storagemap->{identity
});
4414 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4415 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4418 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4419 if $storagemap->{default};
4421 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4422 if $storagemap->{identity
};
4424 $param->{storagemap
} = $storagemap;
4426 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4429 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4434 print "Requesting HA migration for VM $vmid to node $target\n";
4436 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4437 PVE
::Tools
::run_command
($cmd);
4441 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4446 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4450 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4453 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4458 __PACKAGE__-
>register_method({
4459 name
=> 'remote_migrate_vm',
4460 path
=> '{vmid}/remote_migrate',
4464 description
=> "Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!",
4466 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4469 additionalProperties
=> 0,
4471 node
=> get_standard_option
('pve-node'),
4472 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4473 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
4474 'target-endpoint' => get_standard_option
('proxmox-remote', {
4475 description
=> "Remote target endpoint",
4479 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4484 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.",
4488 'target-storage' => get_standard_option
('pve-targetstorage', {
4489 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4492 'target-bridge' => {
4494 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.",
4495 format
=> 'bridge-pair-list',
4498 description
=> "Override I/O bandwidth limit (in KiB/s).",
4502 default => 'migrate limit from datacenter or storage config',
4508 description
=> "the task ID.",
4513 my $rpcenv = PVE
::RPCEnvironment
::get
();
4514 my $authuser = $rpcenv->get_user();
4516 my $source_vmid = extract_param
($param, 'vmid');
4517 my $target_endpoint = extract_param
($param, 'target-endpoint');
4518 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
4520 my $delete = extract_param
($param, 'delete') // 0;
4522 PVE
::Cluster
::check_cfs_quorum
();
4525 my $conf = PVE
::QemuConfig-
>load_config($source_vmid);
4527 PVE
::QemuConfig-
>check_lock($conf);
4529 raise_param_exc
({ vmid
=> "cannot migrate HA-managed VM to remote cluster" })
4530 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
4532 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
4534 # TODO: move this as helper somewhere appropriate?
4536 protocol
=> 'https',
4537 host
=> $remote->{host
},
4538 port
=> $remote->{port
} // 8006,
4539 apitoken
=> $remote->{apitoken
},
4543 if ($fp = $remote->{fingerprint
}) {
4544 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
4547 print "Establishing API connection with remote at '$remote->{host}'\n";
4549 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
4551 if (!defined($fp)) {
4552 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
4553 foreach my $cert (@$cert_info) {
4554 my $filename = $cert->{filename
};
4555 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
4556 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
4558 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
4562 my $repl_conf = PVE
::ReplicationConfig-
>new();
4563 my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1);
4564 die "cannot remote-migrate replicated VM\n" if $is_replicated;
4566 if (PVE
::QemuServer
::check_running
($source_vmid)) {
4567 die "can't migrate running VM without --online\n" if !$param->{online
};
4570 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4571 $param->{online
} = 0;
4574 my $storecfg = PVE
::Storage
::config
();
4575 my $target_storage = extract_param
($param, 'target-storage');
4576 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
4577 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
4580 my $target_bridge = extract_param
($param, 'target-bridge');
4581 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
4582 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
4585 die "remote migration requires explicit storage mapping!\n"
4586 if $storagemap->{identity
};
4588 $param->{storagemap
} = $storagemap;
4589 $param->{bridgemap
} = $bridgemap;
4590 $param->{remote
} = {
4591 conn
=> $conn_args, # re-use fingerprint for tunnel
4592 client
=> $api_client,
4593 vmid
=> $target_vmid,
4595 $param->{migration_type
} = 'websocket';
4596 $param->{'with-local-disks'} = 1;
4597 $param->{delete} = $delete if $delete;
4599 my $cluster_status = $api_client->get("/cluster/status");
4601 foreach my $entry (@$cluster_status) {
4602 next if $entry->{type
} ne 'node';
4603 if ($entry->{local}) {
4604 $target_node = $entry->{name
};
4609 die "couldn't determine endpoint's node name\n"
4610 if !defined($target_node);
4613 PVE
::QemuMigrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
4617 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
4620 return $rpcenv->fork_worker('qmigrate', $source_vmid, $authuser, $worker);
4623 __PACKAGE__-
>register_method({
4625 path
=> '{vmid}/monitor',
4629 description
=> "Execute QEMU monitor commands.",
4631 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4632 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4635 additionalProperties
=> 0,
4637 node
=> get_standard_option
('pve-node'),
4638 vmid
=> get_standard_option
('pve-vmid'),
4641 description
=> "The monitor command.",
4645 returns
=> { type
=> 'string'},
4649 my $rpcenv = PVE
::RPCEnvironment
::get
();
4650 my $authuser = $rpcenv->get_user();
4653 my $command = shift;
4654 return $command =~ m/^\s*info(\s+|$)/
4655 || $command =~ m/^\s*help\s*$/;
4658 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4659 if !&$is_ro($param->{command
});
4661 my $vmid = $param->{vmid
};
4663 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4667 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4669 $res = "ERROR: $@" if $@;
4674 __PACKAGE__-
>register_method({
4675 name
=> 'resize_vm',
4676 path
=> '{vmid}/resize',
4680 description
=> "Extend volume size.",
4682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4685 additionalProperties
=> 0,
4687 node
=> get_standard_option
('pve-node'),
4688 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4689 skiplock
=> get_standard_option
('skiplock'),
4692 description
=> "The disk you want to resize.",
4693 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4697 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4698 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.",
4702 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4710 description
=> "the task ID.",
4715 my $rpcenv = PVE
::RPCEnvironment
::get
();
4717 my $authuser = $rpcenv->get_user();
4719 my $node = extract_param
($param, 'node');
4721 my $vmid = extract_param
($param, 'vmid');
4723 my $digest = extract_param
($param, 'digest');
4725 my $disk = extract_param
($param, 'disk');
4727 my $sizestr = extract_param
($param, 'size');
4729 my $skiplock = extract_param
($param, 'skiplock');
4730 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4731 if $skiplock && $authuser ne 'root@pam';
4733 my $storecfg = PVE
::Storage
::config
();
4735 my $updatefn = sub {
4737 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4739 die "checksum missmatch (file change by other user?)\n"
4740 if $digest && $digest ne $conf->{digest
};
4741 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4743 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4745 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4747 my (undef, undef, undef, undef, undef, undef, $format) =
4748 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4750 my $volid = $drive->{file
};
4752 die "disk '$disk' has no associated volume\n" if !$volid;
4754 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4756 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4758 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4760 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4761 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4763 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4765 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4766 my ($ext, $newsize, $unit) = ($1, $2, $4);
4769 $newsize = $newsize * 1024;
4770 } elsif ($unit eq 'M') {
4771 $newsize = $newsize * 1024 * 1024;
4772 } elsif ($unit eq 'G') {
4773 $newsize = $newsize * 1024 * 1024 * 1024;
4774 } elsif ($unit eq 'T') {
4775 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4778 $newsize += $size if $ext;
4779 $newsize = int($newsize);
4781 die "shrinking disks is not supported\n" if $newsize < $size;
4783 return if $size == $newsize;
4785 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4787 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4789 $drive->{size
} = $newsize;
4790 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4792 PVE
::QemuConfig-
>write_config($vmid, $conf);
4796 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4799 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
4802 __PACKAGE__-
>register_method({
4803 name
=> 'snapshot_list',
4804 path
=> '{vmid}/snapshot',
4806 description
=> "List all snapshots.",
4808 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4811 protected
=> 1, # qemu pid files are only readable by root
4813 additionalProperties
=> 0,
4815 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4816 node
=> get_standard_option
('pve-node'),
4825 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4829 description
=> "Snapshot includes RAM.",
4834 description
=> "Snapshot description.",
4838 description
=> "Snapshot creation time",
4840 renderer
=> 'timestamp',
4844 description
=> "Parent snapshot identifier.",
4850 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4855 my $vmid = $param->{vmid
};
4857 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4858 my $snaphash = $conf->{snapshots
} || {};
4862 foreach my $name (keys %$snaphash) {
4863 my $d = $snaphash->{$name};
4866 snaptime
=> $d->{snaptime
} || 0,
4867 vmstate
=> $d->{vmstate
} ?
1 : 0,
4868 description
=> $d->{description
} || '',
4870 $item->{parent
} = $d->{parent
} if $d->{parent
};
4871 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4875 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4878 digest
=> $conf->{digest
},
4879 running
=> $running,
4880 description
=> "You are here!",
4882 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4884 push @$res, $current;
4889 __PACKAGE__-
>register_method({
4891 path
=> '{vmid}/snapshot',
4895 description
=> "Snapshot a VM.",
4897 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4900 additionalProperties
=> 0,
4902 node
=> get_standard_option
('pve-node'),
4903 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4904 snapname
=> get_standard_option
('pve-snapshot-name'),
4908 description
=> "Save the vmstate",
4913 description
=> "A textual description or comment.",
4919 description
=> "the task ID.",
4924 my $rpcenv = PVE
::RPCEnvironment
::get
();
4926 my $authuser = $rpcenv->get_user();
4928 my $node = extract_param
($param, 'node');
4930 my $vmid = extract_param
($param, 'vmid');
4932 my $snapname = extract_param
($param, 'snapname');
4934 die "unable to use snapshot name 'current' (reserved name)\n"
4935 if $snapname eq 'current';
4937 die "unable to use snapshot name 'pending' (reserved name)\n"
4938 if lc($snapname) eq 'pending';
4941 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4942 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4943 $param->{description
});
4946 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4949 __PACKAGE__-
>register_method({
4950 name
=> 'snapshot_cmd_idx',
4951 path
=> '{vmid}/snapshot/{snapname}',
4958 additionalProperties
=> 0,
4960 vmid
=> get_standard_option
('pve-vmid'),
4961 node
=> get_standard_option
('pve-node'),
4962 snapname
=> get_standard_option
('pve-snapshot-name'),
4971 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4978 push @$res, { cmd
=> 'rollback' };
4979 push @$res, { cmd
=> 'config' };
4984 __PACKAGE__-
>register_method({
4985 name
=> 'update_snapshot_config',
4986 path
=> '{vmid}/snapshot/{snapname}/config',
4990 description
=> "Update snapshot metadata.",
4992 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4995 additionalProperties
=> 0,
4997 node
=> get_standard_option
('pve-node'),
4998 vmid
=> get_standard_option
('pve-vmid'),
4999 snapname
=> get_standard_option
('pve-snapshot-name'),
5003 description
=> "A textual description or comment.",
5007 returns
=> { type
=> 'null' },
5011 my $rpcenv = PVE
::RPCEnvironment
::get
();
5013 my $authuser = $rpcenv->get_user();
5015 my $vmid = extract_param
($param, 'vmid');
5017 my $snapname = extract_param
($param, 'snapname');
5019 return if !defined($param->{description
});
5021 my $updatefn = sub {
5023 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5025 PVE
::QemuConfig-
>check_lock($conf);
5027 my $snap = $conf->{snapshots
}->{$snapname};
5029 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5031 $snap->{description
} = $param->{description
} if defined($param->{description
});
5033 PVE
::QemuConfig-
>write_config($vmid, $conf);
5036 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
5041 __PACKAGE__-
>register_method({
5042 name
=> 'get_snapshot_config',
5043 path
=> '{vmid}/snapshot/{snapname}/config',
5046 description
=> "Get snapshot configuration",
5048 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
5051 additionalProperties
=> 0,
5053 node
=> get_standard_option
('pve-node'),
5054 vmid
=> get_standard_option
('pve-vmid'),
5055 snapname
=> get_standard_option
('pve-snapshot-name'),
5058 returns
=> { type
=> "object" },
5062 my $rpcenv = PVE
::RPCEnvironment
::get
();
5064 my $authuser = $rpcenv->get_user();
5066 my $vmid = extract_param
($param, 'vmid');
5068 my $snapname = extract_param
($param, 'snapname');
5070 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5072 my $snap = $conf->{snapshots
}->{$snapname};
5074 die "snapshot '$snapname' does not exist\n" if !defined($snap);
5079 __PACKAGE__-
>register_method({
5081 path
=> '{vmid}/snapshot/{snapname}/rollback',
5085 description
=> "Rollback VM state to specified snapshot.",
5087 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
5090 additionalProperties
=> 0,
5092 node
=> get_standard_option
('pve-node'),
5093 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5094 snapname
=> get_standard_option
('pve-snapshot-name'),
5097 description
=> "Whether the VM should get started after rolling back successfully."
5098 . " (Note: VMs will be automatically started if the snapshot includes RAM.)",
5106 description
=> "the task ID.",
5111 my $rpcenv = PVE
::RPCEnvironment
::get
();
5113 my $authuser = $rpcenv->get_user();
5115 my $node = extract_param
($param, 'node');
5117 my $vmid = extract_param
($param, 'vmid');
5119 my $snapname = extract_param
($param, 'snapname');
5122 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
5123 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
5125 if ($param->{start
} && !PVE
::QemuServer
::Helpers
::vm_running_locally
($vmid)) {
5126 PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node });
5131 # hold migration lock, this makes sure that nobody create replication snapshots
5132 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
5135 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
5138 __PACKAGE__-
>register_method({
5139 name
=> 'delsnapshot',
5140 path
=> '{vmid}/snapshot/{snapname}',
5144 description
=> "Delete a VM snapshot.",
5146 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
5149 additionalProperties
=> 0,
5151 node
=> get_standard_option
('pve-node'),
5152 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5153 snapname
=> get_standard_option
('pve-snapshot-name'),
5157 description
=> "For removal from config file, even if removing disk snapshots fails.",
5163 description
=> "the task ID.",
5168 my $rpcenv = PVE
::RPCEnvironment
::get
();
5170 my $authuser = $rpcenv->get_user();
5172 my $node = extract_param
($param, 'node');
5174 my $vmid = extract_param
($param, 'vmid');
5176 my $snapname = extract_param
($param, 'snapname');
5179 my $do_delete = sub {
5181 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
5182 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
5186 if ($param->{force
}) {
5189 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
5191 die $err if $lock_obtained;
5192 die "Failed to obtain guest migration lock - replication running?\n";
5197 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
5200 __PACKAGE__-
>register_method({
5202 path
=> '{vmid}/template',
5206 description
=> "Create a Template.",
5208 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
5209 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
5212 additionalProperties
=> 0,
5214 node
=> get_standard_option
('pve-node'),
5215 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
5219 description
=> "If you want to convert only 1 disk to base image.",
5220 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
5227 description
=> "the task ID.",
5232 my $rpcenv = PVE
::RPCEnvironment
::get
();
5234 my $authuser = $rpcenv->get_user();
5236 my $node = extract_param
($param, 'node');
5238 my $vmid = extract_param
($param, 'vmid');
5240 my $disk = extract_param
($param, 'disk');
5242 my $load_and_check = sub {
5243 my $conf = PVE
::QemuConfig-
>load_config($vmid);
5245 PVE
::QemuConfig-
>check_lock($conf);
5247 die "unable to create template, because VM contains snapshots\n"
5248 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
5250 die "you can't convert a template to a template\n"
5251 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
5253 die "you can't convert a VM to template if VM is running\n"
5254 if PVE
::QemuServer
::check_running
($vmid);
5259 $load_and_check->();
5262 PVE
::QemuConfig-
>lock_config($vmid, sub {
5263 my $conf = $load_and_check->();
5265 $conf->{template
} = 1;
5266 PVE
::QemuConfig-
>write_config($vmid, $conf);
5268 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
5272 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
5275 __PACKAGE__-
>register_method({
5276 name
=> 'cloudinit_generated_config_dump',
5277 path
=> '{vmid}/cloudinit/dump',
5280 description
=> "Get automatically generated cloudinit config.",
5282 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
5285 additionalProperties
=> 0,
5287 node
=> get_standard_option
('pve-node'),
5288 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
5290 description
=> 'Config type.',
5292 enum
=> ['user', 'network', 'meta'],
5302 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
5304 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});
5307 __PACKAGE__-
>register_method({
5309 path
=> '{vmid}/mtunnel',
5312 description
=> 'Migration tunnel endpoint - only for internal use by VM migration.',
5316 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
5317 ['perm', '/', [ 'Sys.Incoming' ]],
5319 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
5320 " on '/'. Further permission checks happen during the actual migration.",
5323 additionalProperties
=> 0,
5325 node
=> get_standard_option
('pve-node'),
5326 vmid
=> get_standard_option
('pve-vmid'),
5329 format
=> 'pve-storage-id-list',
5331 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
5335 format
=> 'pve-bridge-id-list',
5337 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
5342 additionalProperties
=> 0,
5344 upid
=> { type
=> 'string' },
5345 ticket
=> { type
=> 'string' },
5346 socket => { type
=> 'string' },
5352 my $rpcenv = PVE
::RPCEnvironment
::get
();
5353 my $authuser = $rpcenv->get_user();
5355 my $node = extract_param
($param, 'node');
5356 my $vmid = extract_param
($param, 'vmid');
5358 my $storages = extract_param
($param, 'storages');
5359 my $bridges = extract_param
($param, 'bridges');
5361 my $nodename = PVE
::INotify
::nodename
();
5363 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5364 if $node ne 'localhost' && $node ne $nodename;
5368 my $storecfg = PVE
::Storage
::config
();
5369 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
5370 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
5373 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
5374 PVE
::Network
::read_bridge_mtu
($bridge);
5377 PVE
::Cluster
::check_cfs_quorum
();
5379 my $lock = 'create';
5380 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, 0, $lock); };
5382 raise_param_exc
({ vmid
=> "unable to create empty VM config - $@"})
5387 storecfg
=> PVE
::Storage
::config
(),
5392 my $run_locked = sub {
5393 my ($code, $params) = @_;
5394 return PVE
::QemuConfig-
>lock_config($state->{vmid
}, sub {
5395 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5397 $state->{conf
} = $conf;
5399 die "Encountered wrong lock - aborting mtunnel command handling.\n"
5400 if $state->{lock} && !PVE
::QemuConfig-
>has_lock($conf, $state->{lock});
5402 return $code->($params);
5410 description
=> 'Full VM config, adapted for target cluster/node',
5412 'firewall-config' => {
5414 description
=> 'VM firewall config',
5419 format
=> PVE
::JSONSchema
::get_standard_option
('pve-qm-image-format'),
5422 format
=> 'pve-storage-id',
5426 description
=> 'parsed drive information without volid and format',
5432 description
=> 'params passed to vm_start_nolock',
5436 description
=> 'migrate_opts passed to vm_start_nolock',
5442 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
5448 description
=> 'remove VM config and disks, aborting migration',
5452 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
5453 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
5454 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
5457 my $cmd_handlers = {
5459 # compared against other end's version
5460 # bump/reset for breaking changes
5461 # bump/bump for opt-in changes
5463 api
=> $PVE::QemuMigrate
::WS_TUNNEL_VERSION
,
5470 # parse and write out VM FW config if given
5471 if (my $fw_conf = $params->{'firewall-config'}) {
5472 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
5479 ipset_comments
=> {},
5481 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
5483 # TODO: add flag for strict parsing?
5484 # TODO: add import sub that does all this given raw content?
5485 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
5486 $vmfw_conf->{vmid
} = $state->{vmid
};
5487 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
5489 $state->{cleanup
}->{fw
} = 1;
5492 my $conf_fn = "incoming/qemu-server/$state->{vmid}.conf";
5493 my $new_conf = PVE
::QemuServer
::parse_vm_config
($conf_fn, $params->{conf
}, 1);
5494 delete $new_conf->{lock};
5495 delete $new_conf->{digest
};
5497 # TODO handle properly?
5498 delete $new_conf->{snapshots
};
5499 delete $new_conf->{parent
};
5500 delete $new_conf->{pending
};
5502 # not handled by update_vm_api
5503 my $vmgenid = delete $new_conf->{vmgenid
};
5504 my $meta = delete $new_conf->{meta
};
5505 my $cloudinit = delete $new_conf->{cloudinit
}; # this is informational only
5506 $new_conf->{skip_cloud_init
} = 1; # re-use image from source side
5508 $new_conf->{vmid
} = $state->{vmid
};
5509 $new_conf->{node
} = $node;
5511 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, 'create');
5514 $update_vm_api->($new_conf, 1);
5517 # revert to locked previous config
5518 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5519 $conf->{lock} = 'create';
5520 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5525 my $conf = PVE
::QemuConfig-
>load_config($state->{vmid
});
5526 $conf->{lock} = 'migrate';
5527 $conf->{vmgenid
} = $vmgenid if defined($vmgenid);
5528 $conf->{meta
} = $meta if defined($meta);
5529 $conf->{cloudinit
} = $cloudinit if defined($cloudinit);
5530 PVE
::QemuConfig-
>write_config($state->{vmid
}, $conf);
5532 $state->{lock} = 'migrate';
5538 return PVE
::StorageTunnel
::handle_bwlimit
($params);
5543 my $format = $params->{format
};
5544 my $storeid = $params->{storage
};
5545 my $drive = $params->{drive
};
5547 $check_storage_access_migrate->($rpcenv, $authuser, $state->{storecfg
}, $storeid, $node);
5550 default => $storeid,
5553 my $source_volumes = {
5564 my $res = PVE
::QemuServer
::vm_migrate_alloc_nbd_disks
($state->{storecfg
}, $state->{vmid
}, $source_volumes, $storagemap);
5565 if (defined($res->{disk
})) {
5566 $state->{cleanup
}->{volumes
}->{$res->{disk
}->{volid
}} = 1;
5567 return $res->{disk
};
5569 die "failed to allocate NBD disk..\n";
5572 'disk-import' => sub {
5575 $check_storage_access_migrate->(
5583 $params->{unix
} = "/run/qemu-server/$state->{vmid}.storage";
5585 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
5587 'query-disk-import' => sub {
5590 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
5595 my $info = PVE
::QemuServer
::vm_start_nolock
(
5599 $params->{start_params
},
5600 $params->{migrate_opts
},
5604 if ($info->{migrate
}->{proto
} ne 'unix') {
5605 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5606 die "migration over non-UNIX sockets not possible\n";
5609 my $socket = $info->{migrate
}->{addr
};
5610 chown $state->{socket_uid
}, -1, $socket;
5611 $state->{sockets
}->{$socket} = 1;
5613 my $unix_sockets = $info->{migrate
}->{unix_sockets
};
5614 foreach my $socket (@$unix_sockets) {
5615 chown $state->{socket_uid
}, -1, $socket;
5616 $state->{sockets
}->{$socket} = 1;
5621 if (PVE
::QemuServer
::qga_check_running
($state->{vmid
})) {
5622 eval { mon_cmd
($state->{vmid
}, "guest-fstrim") };
5623 warn "fstrim failed: $@\n" if $@;
5628 PVE
::QemuServer
::vm_stop
(undef, $state->{vmid
}, 1, 1);
5632 PVE
::QemuServer
::nbd_stop
($state->{vmid
});
5636 if (PVE
::QemuServer
::Helpers
::vm_running_locally
($state->{vmid
})) {
5637 PVE
::QemuServer
::vm_resume
($state->{vmid
}, 1, 1);
5639 die "VM $state->{vmid} not running\n";
5644 PVE
::QemuConfig-
>remove_lock($state->{vmid
}, $state->{lock});
5645 delete $state->{lock};
5651 my $path = $params->{path
};
5653 die "Not allowed to generate ticket for unknown socket '$path'\n"
5654 if !defined($state->{sockets
}->{$path});
5656 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
5661 if ($params->{cleanup
}) {
5662 if ($state->{cleanup
}->{fw
}) {
5663 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
5666 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
5667 print "freeing volume '$volid' as part of cleanup\n";
5668 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
5672 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5675 print "switching to exit-mode, waiting for client to disconnect\n";
5682 my $socket_addr = "/run/qemu-server/$state->{vmid}.mtunnel";
5683 unlink $socket_addr;
5685 $state->{socket} = IO
::Socket
::UNIX-
>new(
5686 Type
=> SOCK_STREAM
(),
5687 Local
=> $socket_addr,
5691 $state->{socket_uid
} = getpwnam('www-data')
5692 or die "Failed to resolve user 'www-data' to numeric UID\n";
5693 chown $state->{socket_uid
}, -1, $socket_addr;
5696 print "mtunnel started\n";
5698 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
5700 warn "Failed to accept tunnel connection - $@\n";
5702 warn "Removing tunnel socket..\n";
5703 unlink $state->{socket};
5705 warn "Removing temporary VM config..\n";
5707 PVE
::QemuServer
::destroy_vm
($state->{storecfg
}, $state->{vmid
}, 1);
5710 die "Exiting mtunnel\n";
5713 $state->{conn
} = $conn;
5715 my $reply_err = sub {
5718 my $reply = JSON
::encode_json
({
5719 success
=> JSON
::false
,
5722 $conn->print("$reply\n");
5726 my $reply_ok = sub {
5729 $res->{success
} = JSON
::true
;
5730 my $reply = JSON
::encode_json
($res);
5731 $conn->print("$reply\n");
5735 while (my $line = <$conn>) {
5738 # untaint, we validate below if needed
5739 ($line) = $line =~ /^(.*)$/;
5740 my $parsed = eval { JSON
::decode_json
($line) };
5742 $reply_err->("failed to parse command - $@");
5746 my $cmd = delete $parsed->{cmd
};
5747 if (!defined($cmd)) {
5748 $reply_err->("'cmd' missing");
5749 } elsif ($state->{exit}) {
5750 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
5752 } elsif (my $handler = $cmd_handlers->{$cmd}) {
5753 print "received command '$cmd'\n";
5755 if ($cmd_desc->{$cmd}) {
5756 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
5760 my $res = $run_locked->($handler, $parsed);
5763 $reply_err->("failed to handle '$cmd' command - $@")
5766 $reply_err->("unknown command '$cmd' given");
5770 if ($state->{exit}) {
5771 print "mtunnel exited\n";
5773 die "mtunnel exited unexpectedly\n";
5777 my $socket_addr = "/run/qemu-server/$vmid.mtunnel";
5778 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
5779 my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);
5784 socket => $socket_addr,
5788 __PACKAGE__-
>register_method({
5789 name
=> 'mtunnelwebsocket',
5790 path
=> '{vmid}/mtunnelwebsocket',
5793 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.",
5794 user
=> 'all', # check inside
5796 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
5798 additionalProperties
=> 0,
5800 node
=> get_standard_option
('pve-node'),
5801 vmid
=> get_standard_option
('pve-vmid'),
5804 description
=> "unix socket to forward to",
5808 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
5815 port
=> { type
=> 'string', optional
=> 1 },
5816 socket => { type
=> 'string', optional
=> 1 },
5822 my $rpcenv = PVE
::RPCEnvironment
::get
();
5823 my $authuser = $rpcenv->get_user();
5825 my $nodename = PVE
::INotify
::nodename
();
5826 my $node = extract_param
($param, 'node');
5828 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
5829 if $node ne 'localhost' && $node ne $nodename;
5831 my $vmid = $param->{vmid
};
5833 PVE
::QemuConfig-
>load_config($vmid);
5835 my $socket = $param->{socket};
5836 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
5838 return { socket => $socket };