1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::CPUConfig
;
25 use PVE
::QemuServer
::Drive
;
26 use PVE
::QemuServer
::ImportDisk
;
27 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::QemuServer
::Machine
;
30 use PVE
::RPCEnvironment
;
31 use PVE
::AccessControl
;
35 use PVE
::API2
::Firewall
::VM
;
36 use PVE
::API2
::Qemu
::Agent
;
37 use PVE
::VZDump
::Plugin
;
38 use PVE
::DataCenterConfig
;
43 if (!$ENV{PVE_GENERATING_DOCS
}) {
44 require PVE
::HA
::Env
::PVE2
;
45 import PVE
::HA
::Env
::PVE2
;
46 require PVE
::HA
::Config
;
47 import PVE
::HA
::Config
;
51 use Data
::Dumper
; # fixme: remove
53 use base
qw(PVE::RESTHandler);
55 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.";
57 my $resolve_cdrom_alias = sub {
60 if (my $value = $param->{cdrom
}) {
61 $value .= ",media=cdrom" if $value !~ m/media=/;
62 $param->{ide2
} = $value;
63 delete $param->{cdrom
};
67 # Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
68 my $foreach_volume_with_alloc = sub {
69 my ($param, $func) = @_;
71 for my $opt (sort keys $param->%*) {
72 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
74 my $drive = PVE
::QemuServer
::Drive
::parse_drive
($opt, $param->{$opt}, 1);
77 $func->($opt, $drive);
81 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
83 my $check_drive_param = sub {
84 my ($param, $storecfg, $extra_checks) = @_;
86 for my $opt (sort keys $param->%*) {
87 next if !PVE
::QemuServer
::is_valid_drivename
($opt);
89 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
90 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
92 if ($drive->{'import-from'}) {
93 if ($drive->{file
} !~ $NEW_DISK_RE || $3 != 0) {
95 $opt => "'import-from' requires special syntax - ".
96 "use <storage ID>:0,import-from=<source>",
100 if ($opt eq 'efidisk0') {
101 for my $required (qw(efitype pre-enrolled-keys)) {
102 if (!defined($drive->{$required})) {
104 $opt => "need to specify '$required' when using 'import-from'",
108 } elsif ($opt eq 'tpmstate0') {
109 raise_param_exc
({ $opt => "need to specify 'version' when using 'import-from'" })
110 if !defined($drive->{version
});
114 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
116 $extra_checks->($drive) if $extra_checks;
118 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive, 1);
122 my $check_storage_access = sub {
123 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
125 $foreach_volume_with_alloc->($settings, sub {
126 my ($ds, $drive) = @_;
128 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
130 my $volid = $drive->{file
};
131 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
133 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
135 } elsif ($isCDROM && ($volid eq 'cdrom')) {
136 $rpcenv->check($authuser, "/", ['Sys.Console']);
137 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
138 my ($storeid, $size) = ($2 || $default_storage, $3);
139 die "no storage ID specified (and no default storage)\n" if !$storeid;
140 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
141 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
142 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
143 if !$scfg->{content
}->{images
};
145 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
147 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
148 raise_param_exc
({ $ds => "content type needs to be 'images' or 'iso'" })
149 if $vtype ne 'images' && $vtype ne 'iso';
153 if (my $src_image = $drive->{'import-from'}) {
155 if (PVE
::Storage
::parse_volume_id
($src_image, 1)) { # PVE-managed volume
156 (my $vtype, undef, $src_vmid) = PVE
::Storage
::parse_volname
($storecfg, $src_image);
157 raise_param_exc
({ $ds => "$src_image has wrong type '$vtype' - not an image" })
158 if $vtype ne 'images';
161 if ($src_vmid) { # might be actively used by VM and will be copied via clone_disk()
162 $rpcenv->check($authuser, "/vms/${src_vmid}", ['VM.Clone']);
164 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $src_image);
169 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
170 if defined($settings->{vmstatestorage
});
173 my $check_storage_access_clone = sub {
174 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
178 PVE
::QemuConfig-
>foreach_volume($conf, sub {
179 my ($ds, $drive) = @_;
181 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
183 my $volid = $drive->{file
};
185 return if !$volid || $volid eq 'none';
188 if ($volid eq 'cdrom') {
189 $rpcenv->check($authuser, "/", ['Sys.Console']);
191 # we simply allow access
192 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
193 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
194 $sharedvm = 0 if !$scfg->{shared
};
198 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
199 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
200 $sharedvm = 0 if !$scfg->{shared
};
202 $sid = $storage if $storage;
203 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
207 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
208 if defined($conf->{vmstatestorage
});
213 my $check_storage_access_migrate = sub {
214 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
216 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
218 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
220 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
221 die "storage '$storage' does not support vm images\n"
222 if !$scfg->{content
}->{images
};
225 my $import_from_volid = sub {
226 my ($storecfg, $src_volid, $dest_info, $vollist) = @_;
228 die "could not get size of $src_volid\n"
229 if !PVE
::Storage
::volume_size_info
($storecfg, $src_volid, 10);
231 die "cannot import from cloudinit disk\n"
232 if PVE
::QemuServer
::Drive
::drive_is_cloudinit
({ file
=> $src_volid });
234 my $src_vmid = (PVE
::Storage
::parse_volname
($storecfg, $src_volid))[2];
236 my $src_vm_state = sub {
237 my $exists = $src_vmid && PVE
::Cluster
::get_vmlist
()->{ids
}->{$src_vmid} ?
1 : 0;
241 eval { PVE
::QemuConfig
::assert_config_exists_on_node
($src_vmid); };
242 die "owner VM $src_vmid not on local node\n" if $@;
243 $runs = PVE
::QemuServer
::Helpers
::vm_running_locally
($src_vmid) || 0;
246 return ($exists, $runs);
249 my ($src_vm_exists, $running) = $src_vm_state->();
251 die "cannot import from '$src_volid' - full clone feature is not supported\n"
252 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $src_volid, undef, $running);
255 my ($src_vm_exists_now, $running_now) = $src_vm_state->();
257 die "owner VM $src_vmid changed state unexpectedly\n"
258 if $src_vm_exists_now != $src_vm_exists || $running_now != $running;
260 my $src_conf = $src_vm_exists_now ? PVE
::QemuConfig-
>load_config($src_vmid) : {};
262 my $src_drive = { file
=> $src_volid };
264 PVE
::QemuConfig-
>foreach_volume($src_conf, sub {
265 my ($ds, $drive) = @_;
267 return if $src_drivename;
269 if ($drive->{file
} eq $src_volid) {
271 $src_drivename = $ds;
277 running
=> $running_now,
278 drivename
=> $src_drivename,
283 my ($src_storeid) = PVE
::Storage
::parse_volume_id
($src_volid);
285 return PVE
::QemuServer
::clone_disk
(
294 PVE
::Storage
::get_bandwidth_limit
('clone', [$src_storeid, $dest_info->{storage
}]),
300 $cloned = PVE
::QemuConfig-
>lock_config_full($src_vmid, 30, $clonefn);
301 } elsif ($src_vmid) {
302 $cloned = PVE
::QemuConfig-
>lock_config_shared($src_vmid, 30, $clonefn);
304 $cloned = $clonefn->();
307 return $cloned->@{qw(file size)};
310 # Note: $pool is only needed when creating a VM, because pool permissions
311 # are automatically inherited if VM already exists inside a pool.
312 my $create_disks = sub {
313 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
320 my ($ds, $disk) = @_;
322 my $volid = $disk->{file
};
323 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
325 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
326 delete $disk->{size
};
327 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
328 } elsif (defined($volname) && $volname eq 'cloudinit') {
329 $storeid = $storeid // $default_storage;
330 die "no storage ID specified (and no default storage)\n" if !$storeid;
331 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
332 my $name = "vm-$vmid-cloudinit";
336 $fmt = $disk->{format
} // "qcow2";
339 $fmt = $disk->{format
} // "raw";
342 # Initial disk created with 4 MB and aligned to 4MB on regeneration
343 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
344 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
345 $disk->{file
} = $volid;
346 $disk->{media
} = 'cdrom';
347 push @$vollist, $volid;
348 delete $disk->{format
}; # no longer needed
349 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
350 print "$ds: successfully created disk '$res->{$ds}'\n";
351 } elsif ($volid =~ $NEW_DISK_RE) {
352 my ($storeid, $size) = ($2 || $default_storage, $3);
353 die "no storage ID specified (and no default storage)\n" if !$storeid;
355 if (my $source = delete $disk->{'import-from'}) {
358 if (PVE
::Storage
::parse_volume_id
($source, 1)) { # PVE-managed volume
363 format
=> $disk->{format
},
366 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf, $disk)
367 if $ds eq 'efidisk0';
369 ($dst_volid, $size) = eval {
370 $import_from_volid->($storecfg, $source, $dest_info, $vollist);
372 die "cannot import from '$source' - $@" if $@;
374 $source = PVE
::Storage
::abs_filesystem_path
($storecfg, $source, 1);
375 $size = PVE
::Storage
::file_size_info
($source);
376 die "could not get file size of $source\n" if !$size;
378 (undef, $dst_volid) = PVE
::QemuServer
::ImportDisk
::do_import
(
384 format
=> $disk->{format
},
385 'skip-config-update' => 1,
388 push @$vollist, $dst_volid;
391 $disk->{file
} = $dst_volid;
392 $disk->{size
} = $size;
393 delete $disk->{format
}; # no longer needed
394 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
396 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
397 my $fmt = $disk->{format
} || $defformat;
399 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
402 if ($ds eq 'efidisk0') {
403 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
404 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
405 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
406 } elsif ($ds eq 'tpmstate0') {
407 # swtpm can only use raw volumes, and uses a fixed size
408 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
409 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
411 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
413 push @$vollist, $volid;
414 $disk->{file
} = $volid;
415 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
416 delete $disk->{format
}; # no longer needed
417 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
420 print "$ds: successfully created disk '$res->{$ds}'\n";
422 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
424 my ($vtype) = PVE
::Storage
::parse_volname
($storecfg, $volid);
425 die "cannot use volume $volid - content type needs to be 'images' or 'iso'"
426 if $vtype ne 'images' && $vtype ne 'iso';
429 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
431 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
432 die "volume $volid does not exist\n" if !$size;
433 $disk->{size
} = $size;
435 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
439 eval { $foreach_volume_with_alloc->($settings, $code); };
441 # free allocated images on error
443 syslog
('err', "VM $vmid creating disks failed");
444 foreach my $volid (@$vollist) {
445 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
451 return ($vollist, $res);
454 my $check_cpu_model_access = sub {
455 my ($rpcenv, $authuser, $new, $existing) = @_;
457 return if !defined($new->{cpu
});
459 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
460 return if !$cpu || !$cpu->{cputype
}; # always allow default
461 my $cputype = $cpu->{cputype
};
463 if ($existing && $existing->{cpu
}) {
464 # changing only other settings doesn't require permissions for CPU model
465 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
466 return if $existingCpu->{cputype
} eq $cputype;
469 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
470 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
485 my $memoryoptions = {
491 my $hwtypeoptions = {
504 my $generaloptions = {
511 'migrate_downtime' => 1,
512 'migrate_speed' => 1,
525 my $vmpoweroptions = {
532 'vmstatestorage' => 1,
535 my $cloudinitoptions = {
545 my $check_vm_create_serial_perm = sub {
546 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
548 return 1 if $authuser eq 'root@pam';
550 foreach my $opt (keys %{$param}) {
551 next if $opt !~ m/^serial\d+$/;
553 if ($param->{$opt} eq 'socket') {
554 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
556 die "only root can set '$opt' config for real devices\n";
563 my $check_vm_create_usb_perm = sub {
564 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
566 return 1 if $authuser eq 'root@pam';
568 foreach my $opt (keys %{$param}) {
569 next if $opt !~ m/^usb\d+$/;
571 if ($param->{$opt} =~ m/spice/) {
572 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
574 die "only root can set '$opt' config for real devices\n";
581 my $check_vm_modify_config_perm = sub {
582 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
584 return 1 if $authuser eq 'root@pam';
586 foreach my $opt (@$key_list) {
587 # some checks (e.g., disk, serial port, usb) need to be done somewhere
588 # else, as there the permission can be value dependend
589 next if PVE
::QemuServer
::is_valid_drivename
($opt);
590 next if $opt eq 'cdrom';
591 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
594 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
595 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
596 } elsif ($memoryoptions->{$opt}) {
597 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
598 } elsif ($hwtypeoptions->{$opt}) {
599 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
600 } elsif ($generaloptions->{$opt}) {
601 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
602 # special case for startup since it changes host behaviour
603 if ($opt eq 'startup') {
604 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
606 } elsif ($vmpoweroptions->{$opt}) {
607 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
608 } elsif ($diskoptions->{$opt}) {
609 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
610 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
611 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
612 } elsif ($cloudinitoptions->{$opt}) {
613 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
614 } elsif ($opt eq 'vmstate') {
615 # the user needs Disk and PowerMgmt privileges to change the vmstate
616 # also needs privileges on the storage, that will be checked later
617 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
619 # catches hostpci\d+, args, lock, etc.
620 # new options will be checked here
621 die "only root can set '$opt' config\n";
628 __PACKAGE__-
>register_method({
632 description
=> "Virtual machine index (per node).",
634 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
638 protected
=> 1, # qemu pid files are only readable by root
640 additionalProperties
=> 0,
642 node
=> get_standard_option
('pve-node'),
646 description
=> "Determine the full status of active VMs.",
654 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
656 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
661 my $rpcenv = PVE
::RPCEnvironment
::get
();
662 my $authuser = $rpcenv->get_user();
664 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
667 foreach my $vmid (keys %$vmstatus) {
668 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
670 my $data = $vmstatus->{$vmid};
677 my $parse_restore_archive = sub {
678 my ($storecfg, $archive) = @_;
680 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
682 if (defined($archive_storeid)) {
683 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
684 if ($scfg->{type
} eq 'pbs') {
691 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
699 __PACKAGE__-
>register_method({
703 description
=> "Create or restore a virtual machine.",
705 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
706 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
707 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
708 user
=> 'all', # check inside
713 additionalProperties
=> 0,
714 properties
=> PVE
::QemuServer
::json_config_properties
(
716 node
=> get_standard_option
('pve-node'),
717 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
719 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.",
723 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
725 storage
=> get_standard_option
('pve-storage-id', {
726 description
=> "Default storage.",
728 completion
=> \
&PVE
::QemuServer
::complete_storage
,
733 description
=> "Allow to overwrite existing VM.",
734 requires
=> 'archive',
739 description
=> "Assign a unique random ethernet address.",
740 requires
=> 'archive',
745 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
746 requires
=> 'archive',
750 type
=> 'string', format
=> 'pve-poolid',
751 description
=> "Add the VM to the specified pool.",
754 description
=> "Override I/O bandwidth limit (in KiB/s).",
758 default => 'restore limit from datacenter or storage config',
764 description
=> "Start VM after it was created successfully.",
776 my $rpcenv = PVE
::RPCEnvironment
::get
();
777 my $authuser = $rpcenv->get_user();
779 my $node = extract_param
($param, 'node');
780 my $vmid = extract_param
($param, 'vmid');
782 my $archive = extract_param
($param, 'archive');
783 my $is_restore = !!$archive;
785 my $bwlimit = extract_param
($param, 'bwlimit');
786 my $force = extract_param
($param, 'force');
787 my $pool = extract_param
($param, 'pool');
788 my $start_after_create = extract_param
($param, 'start');
789 my $storage = extract_param
($param, 'storage');
790 my $unique = extract_param
($param, 'unique');
791 my $live_restore = extract_param
($param, 'live-restore');
793 if (defined(my $ssh_keys = $param->{sshkeys
})) {
794 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
795 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
798 PVE
::Cluster
::check_cfs_quorum
();
800 my $filename = PVE
::QemuConfig-
>config_file($vmid);
801 my $storecfg = PVE
::Storage
::config
();
803 if (defined($pool)) {
804 $rpcenv->check_pool_exist($pool);
807 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
808 if defined($storage);
810 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
812 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
814 } elsif ($archive && $force && (-f
$filename) &&
815 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
816 # OK: user has VM.Backup permissions, and want to restore an existing VM
822 my $keystr = join(' ', keys %$param);
823 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
825 if ($archive eq '-') {
826 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
827 $archive = { type
=> 'pipe' };
829 PVE
::Storage
::check_volume_access
(
838 $archive = $parse_restore_archive->($storecfg, $archive);
842 if (scalar(keys $param->%*) > 0) {
843 &$resolve_cdrom_alias($param);
845 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
847 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
849 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
850 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
852 &$check_cpu_model_access($rpcenv, $authuser, $param);
854 $check_drive_param->($param, $storecfg);
856 PVE
::QemuServer
::add_random_macs
($param);
859 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
861 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
862 die "$emsg $@" if $@;
864 my $restored_data = 0;
865 my $restorefn = sub {
866 my $conf = PVE
::QemuConfig-
>load_config($vmid);
868 PVE
::QemuConfig-
>check_protection($conf, $emsg);
870 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
873 my $restore_options = {
878 live
=> $live_restore,
880 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
881 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
883 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
884 } elsif ($archive->{type
} eq 'pbs') {
885 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
887 die "unknown backup archive type\n";
891 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
892 # Convert restored VM to template if backup was VM template
893 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
894 warn "Convert to template.\n";
895 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
900 # ensure no old replication state are exists
901 PVE
::ReplicationState
::delete_guest_states
($vmid);
903 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
905 if ($start_after_create && !$live_restore) {
906 print "Execute autostart\n";
907 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
913 # ensure no old replication state are exists
914 PVE
::ReplicationState
::delete_guest_states
($vmid);
918 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
920 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
924 ($vollist, my $created_opts) = $create_disks->(
935 $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
937 if (!$conf->{boot
}) {
938 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
939 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
942 # auto generate uuid if user did not specify smbios1 option
943 if (!$conf->{smbios1
}) {
944 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
947 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
948 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
951 my $machine = $conf->{machine
};
952 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
953 # always pin Windows' machine version on create, they get to easily confused
954 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
955 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
959 PVE
::QemuConfig-
>write_config($vmid, $conf);
965 foreach my $volid (@$vollist) {
966 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
972 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
975 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
977 if ($start_after_create) {
978 print "Execute autostart\n";
979 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
984 my ($code, $worker_name);
986 $worker_name = 'qmrestore';
988 eval { $restorefn->() };
990 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
992 if ($restored_data) {
993 warn "error after data was restored, VM disks should be OK but config may "
994 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
996 warn "error before or during data restore, some or all disks were not "
997 ."completely restored. VM $vmid state is NOT cleaned up.\n";
1003 $worker_name = 'qmcreate';
1005 eval { $createfn->() };
1008 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
1009 unlink($conffile) or die "failed to remove config file: $!\n";
1017 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1020 __PACKAGE__-
>register_method({
1025 description
=> "Directory index",
1030 additionalProperties
=> 0,
1032 node
=> get_standard_option
('pve-node'),
1033 vmid
=> get_standard_option
('pve-vmid'),
1041 subdir
=> { type
=> 'string' },
1044 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1050 { subdir
=> 'config' },
1051 { subdir
=> 'pending' },
1052 { subdir
=> 'status' },
1053 { subdir
=> 'unlink' },
1054 { subdir
=> 'vncproxy' },
1055 { subdir
=> 'termproxy' },
1056 { subdir
=> 'migrate' },
1057 { subdir
=> 'resize' },
1058 { subdir
=> 'move' },
1059 { subdir
=> 'rrd' },
1060 { subdir
=> 'rrddata' },
1061 { subdir
=> 'monitor' },
1062 { subdir
=> 'agent' },
1063 { subdir
=> 'snapshot' },
1064 { subdir
=> 'spiceproxy' },
1065 { subdir
=> 'sendkey' },
1066 { subdir
=> 'firewall' },
1072 __PACKAGE__-
>register_method ({
1073 subclass
=> "PVE::API2::Firewall::VM",
1074 path
=> '{vmid}/firewall',
1077 __PACKAGE__-
>register_method ({
1078 subclass
=> "PVE::API2::Qemu::Agent",
1079 path
=> '{vmid}/agent',
1082 __PACKAGE__-
>register_method({
1084 path
=> '{vmid}/rrd',
1086 protected
=> 1, # fixme: can we avoid that?
1088 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1090 description
=> "Read VM RRD statistics (returns PNG)",
1092 additionalProperties
=> 0,
1094 node
=> get_standard_option
('pve-node'),
1095 vmid
=> get_standard_option
('pve-vmid'),
1097 description
=> "Specify the time frame you are interested in.",
1099 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1102 description
=> "The list of datasources you want to display.",
1103 type
=> 'string', format
=> 'pve-configid-list',
1106 description
=> "The RRD consolidation function",
1108 enum
=> [ 'AVERAGE', 'MAX' ],
1116 filename
=> { type
=> 'string' },
1122 return PVE
::RRD
::create_rrd_graph
(
1123 "pve2-vm/$param->{vmid}", $param->{timeframe
},
1124 $param->{ds
}, $param->{cf
});
1128 __PACKAGE__-
>register_method({
1130 path
=> '{vmid}/rrddata',
1132 protected
=> 1, # fixme: can we avoid that?
1134 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1136 description
=> "Read VM RRD statistics",
1138 additionalProperties
=> 0,
1140 node
=> get_standard_option
('pve-node'),
1141 vmid
=> get_standard_option
('pve-vmid'),
1143 description
=> "Specify the time frame you are interested in.",
1145 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
1148 description
=> "The RRD consolidation function",
1150 enum
=> [ 'AVERAGE', 'MAX' ],
1165 return PVE
::RRD
::create_rrd_data
(
1166 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
1170 __PACKAGE__-
>register_method({
1171 name
=> 'vm_config',
1172 path
=> '{vmid}/config',
1175 description
=> "Get the virtual machine configuration with pending configuration " .
1176 "changes applied. Set the 'current' parameter to get the current configuration instead.",
1178 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1181 additionalProperties
=> 0,
1183 node
=> get_standard_option
('pve-node'),
1184 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1186 description
=> "Get current values (instead of pending values).",
1191 snapshot
=> get_standard_option
('pve-snapshot-name', {
1192 description
=> "Fetch config values from given snapshot.",
1195 my ($cmd, $pname, $cur, $args) = @_;
1196 PVE
::QemuConfig-
>snapshot_list($args->[0]);
1202 description
=> "The VM configuration.",
1204 properties
=> PVE
::QemuServer
::json_config_properties
({
1207 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1214 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1215 current
=> "cannot use 'snapshot' parameter with 'current'"})
1216 if ($param->{snapshot
} && $param->{current
});
1219 if ($param->{snapshot
}) {
1220 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1222 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1224 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1229 __PACKAGE__-
>register_method({
1230 name
=> 'vm_pending',
1231 path
=> '{vmid}/pending',
1234 description
=> "Get the virtual machine configuration with both current and pending values.",
1236 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1239 additionalProperties
=> 0,
1241 node
=> get_standard_option
('pve-node'),
1242 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1251 description
=> "Configuration option name.",
1255 description
=> "Current value.",
1260 description
=> "Pending value.",
1265 description
=> "Indicates a pending delete request if present and not 0. " .
1266 "The value 2 indicates a force-delete request.",
1278 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1280 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1282 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1283 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1285 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1288 # POST/PUT {vmid}/config implementation
1290 # The original API used PUT (idempotent) an we assumed that all operations
1291 # are fast. But it turned out that almost any configuration change can
1292 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1293 # time to complete and have side effects (not idempotent).
1295 # The new implementation uses POST and forks a worker process. We added
1296 # a new option 'background_delay'. If specified we wait up to
1297 # 'background_delay' second for the worker task to complete. It returns null
1298 # if the task is finished within that time, else we return the UPID.
1300 my $update_vm_api = sub {
1301 my ($param, $sync) = @_;
1303 my $rpcenv = PVE
::RPCEnvironment
::get
();
1305 my $authuser = $rpcenv->get_user();
1307 my $node = extract_param
($param, 'node');
1309 my $vmid = extract_param
($param, 'vmid');
1311 my $digest = extract_param
($param, 'digest');
1313 my $background_delay = extract_param
($param, 'background_delay');
1315 if (defined(my $cipassword = $param->{cipassword
})) {
1316 # Same logic as in cloud-init (but with the regex fixed...)
1317 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1318 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1321 my @paramarr = (); # used for log message
1322 foreach my $key (sort keys %$param) {
1323 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1324 push @paramarr, "-$key", $value;
1327 my $skiplock = extract_param
($param, 'skiplock');
1328 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1329 if $skiplock && $authuser ne 'root@pam';
1331 my $delete_str = extract_param
($param, 'delete');
1333 my $revert_str = extract_param
($param, 'revert');
1335 my $force = extract_param
($param, 'force');
1337 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1338 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1339 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1342 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1344 my $storecfg = PVE
::Storage
::config
();
1346 my $defaults = PVE
::QemuServer
::load_defaults
();
1348 &$resolve_cdrom_alias($param);
1350 # now try to verify all parameters
1353 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1354 if (!PVE
::QemuServer
::option_exists
($opt)) {
1355 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1358 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1359 "-revert $opt' at the same time" })
1360 if defined($param->{$opt});
1362 $revert->{$opt} = 1;
1366 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1367 $opt = 'ide2' if $opt eq 'cdrom';
1369 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1370 "-delete $opt' at the same time" })
1371 if defined($param->{$opt});
1373 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1374 "-revert $opt' at the same time" })
1377 if (!PVE
::QemuServer
::option_exists
($opt)) {
1378 raise_param_exc
({ delete => "unknown option '$opt'" });
1384 my $repl_conf = PVE
::ReplicationConfig-
>new();
1385 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1386 my $check_replication = sub {
1388 return if !$is_replicated;
1389 my $volid = $drive->{file
};
1390 return if !$volid || !($drive->{replicate
}//1);
1391 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1393 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1394 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1395 if !defined($storeid);
1397 return if defined($volname) && $volname eq 'cloudinit';
1400 if ($volid =~ $NEW_DISK_RE) {
1402 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1404 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1406 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1407 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1408 return if $scfg->{shared
};
1409 die "cannot add non-replicatable volume to a replicated VM\n";
1412 $check_drive_param->($param, $storecfg, $check_replication);
1414 foreach my $opt (keys %$param) {
1415 if ($opt =~ m/^net(\d+)$/) {
1417 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1418 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1419 } elsif ($opt eq 'vmgenid') {
1420 if ($param->{$opt} eq '1') {
1421 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1423 } elsif ($opt eq 'hookscript') {
1424 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1425 raise_param_exc
({ $opt => $@ }) if $@;
1429 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1431 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1433 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1435 my $updatefn = sub {
1437 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1439 die "checksum missmatch (file change by other user?)\n"
1440 if $digest && $digest ne $conf->{digest
};
1442 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1444 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1445 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1446 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1447 delete $conf->{lock}; # for check lock check, not written out
1448 push @delete, 'lock'; # this is the real deal to write it out
1450 push @delete, 'runningmachine' if $conf->{runningmachine
};
1451 push @delete, 'runningcpu' if $conf->{runningcpu
};
1454 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1456 foreach my $opt (keys %$revert) {
1457 if (defined($conf->{$opt})) {
1458 $param->{$opt} = $conf->{$opt};
1459 } elsif (defined($conf->{pending
}->{$opt})) {
1464 if ($param->{memory
} || defined($param->{balloon
})) {
1465 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1466 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1468 die "balloon value too large (must be smaller than assigned memory)\n"
1469 if $balloon && $balloon > $maxmem;
1472 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1476 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1478 # write updates to pending section
1480 my $modified = {}; # record what $option we modify
1483 if (my $boot = $conf->{boot
}) {
1484 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1485 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1487 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1489 my $check_drive_perms = sub {
1490 my ($opt, $val) = @_;
1491 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val, 1);
1492 # FIXME: cloudinit: CDROM or Disk?
1493 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1494 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1496 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1500 foreach my $opt (@delete) {
1501 $modified->{$opt} = 1;
1502 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1504 # value of what we want to delete, independent if pending or not
1505 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1506 if (!defined($val)) {
1507 warn "cannot delete '$opt' - not set in current configuration!\n";
1508 $modified->{$opt} = 0;
1511 my $is_pending_val = defined($conf->{pending
}->{$opt});
1512 delete $conf->{pending
}->{$opt};
1514 # remove from bootorder if necessary
1515 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1516 @bootorder = grep {$_ ne $opt} @bootorder;
1517 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1518 $modified->{boot
} = 1;
1521 if ($opt =~ m/^unused/) {
1522 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1523 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1524 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1525 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1526 delete $conf->{$opt};
1527 PVE
::QemuConfig-
>write_config($vmid, $conf);
1529 } elsif ($opt eq 'vmstate') {
1530 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1531 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1532 delete $conf->{$opt};
1533 PVE
::QemuConfig-
>write_config($vmid, $conf);
1535 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1536 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1537 $check_drive_perms->($opt, $val);
1538 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1540 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1541 PVE
::QemuConfig-
>write_config($vmid, $conf);
1542 } elsif ($opt =~ m/^serial\d+$/) {
1543 if ($val eq 'socket') {
1544 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1545 } elsif ($authuser ne 'root@pam') {
1546 die "only root can delete '$opt' config for real devices\n";
1548 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1549 PVE
::QemuConfig-
>write_config($vmid, $conf);
1550 } elsif ($opt =~ m/^usb\d+$/) {
1551 if ($val =~ m/spice/) {
1552 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1553 } elsif ($authuser ne 'root@pam') {
1554 die "only root can delete '$opt' config for real devices\n";
1556 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1557 PVE
::QemuConfig-
>write_config($vmid, $conf);
1559 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1560 PVE
::QemuConfig-
>write_config($vmid, $conf);
1564 foreach my $opt (keys %$param) { # add/change
1565 $modified->{$opt} = 1;
1566 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1567 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1569 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1571 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1573 if ($conf->{$opt}) {
1574 $check_drive_perms->($opt, $conf->{$opt});
1578 $check_drive_perms->($opt, $param->{$opt});
1579 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1580 if defined($conf->{pending
}->{$opt});
1582 my (undef, $created_opts) = $create_disks->(
1590 {$opt => $param->{$opt}},
1592 $conf->{pending
}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
1594 # default legacy boot order implies all cdroms anyway
1596 # append new CD drives to bootorder to mark them bootable
1597 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt}, 1);
1598 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1599 push @bootorder, $opt;
1600 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1601 $modified->{boot
} = 1;
1604 } elsif ($opt =~ m/^serial\d+/) {
1605 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1606 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1607 } elsif ($authuser ne 'root@pam') {
1608 die "only root can modify '$opt' config for real devices\n";
1610 $conf->{pending
}->{$opt} = $param->{$opt};
1611 } elsif ($opt =~ m/^usb\d+/) {
1612 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1613 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1614 } elsif ($authuser ne 'root@pam') {
1615 die "only root can modify '$opt' config for real devices\n";
1617 $conf->{pending
}->{$opt} = $param->{$opt};
1619 $conf->{pending
}->{$opt} = $param->{$opt};
1621 if ($opt eq 'boot') {
1622 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1623 if ($new_bootcfg->{order
}) {
1624 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1625 for my $dev (@devs) {
1626 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev} || $param->{$dev};
1627 my $deleted = grep {$_ eq $dev} @delete;
1628 die "invalid bootorder: device '$dev' does not exist'\n"
1629 if !$exists || $deleted;
1632 # remove legacy boot order settings if new one set
1633 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1634 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1635 if $conf->{bootdisk
};
1639 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1640 PVE
::QemuConfig-
>write_config($vmid, $conf);
1643 # remove pending changes when nothing changed
1644 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1645 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1646 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1648 return if !scalar(keys %{$conf->{pending
}});
1650 my $running = PVE
::QemuServer
::check_running
($vmid);
1652 # apply pending changes
1654 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1658 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1660 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1662 raise_param_exc
($errors) if scalar(keys %$errors);
1671 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1673 if ($background_delay) {
1675 # Note: It would be better to do that in the Event based HTTPServer
1676 # to avoid blocking call to sleep.
1678 my $end_time = time() + $background_delay;
1680 my $task = PVE
::Tools
::upid_decode
($upid);
1683 while (time() < $end_time) {
1684 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1686 sleep(1); # this gets interrupted when child process ends
1690 my $status = PVE
::Tools
::upid_read_status
($upid);
1691 return if !PVE
::Tools
::upid_status_is_error
($status);
1692 die "failed to update VM $vmid: $status\n";
1700 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1703 my $vm_config_perm_list = [
1708 'VM.Config.Network',
1710 'VM.Config.Options',
1711 'VM.Config.Cloudinit',
1714 __PACKAGE__-
>register_method({
1715 name
=> 'update_vm_async',
1716 path
=> '{vmid}/config',
1720 description
=> "Set virtual machine options (asynchrounous API).",
1722 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1725 additionalProperties
=> 0,
1726 properties
=> PVE
::QemuServer
::json_config_properties
(
1728 node
=> get_standard_option
('pve-node'),
1729 vmid
=> get_standard_option
('pve-vmid'),
1730 skiplock
=> get_standard_option
('skiplock'),
1732 type
=> 'string', format
=> 'pve-configid-list',
1733 description
=> "A list of settings you want to delete.",
1737 type
=> 'string', format
=> 'pve-configid-list',
1738 description
=> "Revert a pending change.",
1743 description
=> $opt_force_description,
1745 requires
=> 'delete',
1749 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1753 background_delay
=> {
1755 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1761 1, # with_disk_alloc
1768 code
=> $update_vm_api,
1771 __PACKAGE__-
>register_method({
1772 name
=> 'update_vm',
1773 path
=> '{vmid}/config',
1777 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1779 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1782 additionalProperties
=> 0,
1783 properties
=> PVE
::QemuServer
::json_config_properties
(
1785 node
=> get_standard_option
('pve-node'),
1786 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1787 skiplock
=> get_standard_option
('skiplock'),
1789 type
=> 'string', format
=> 'pve-configid-list',
1790 description
=> "A list of settings you want to delete.",
1794 type
=> 'string', format
=> 'pve-configid-list',
1795 description
=> "Revert a pending change.",
1800 description
=> $opt_force_description,
1802 requires
=> 'delete',
1806 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1811 1, # with_disk_alloc
1814 returns
=> { type
=> 'null' },
1817 &$update_vm_api($param, 1);
1822 __PACKAGE__-
>register_method({
1823 name
=> 'destroy_vm',
1828 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1829 ." and firewall rules",
1831 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1834 additionalProperties
=> 0,
1836 node
=> get_standard_option
('pve-node'),
1837 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1838 skiplock
=> get_standard_option
('skiplock'),
1841 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1844 'destroy-unreferenced-disks' => {
1846 description
=> "If set, destroy additionally all disks not referenced in the config"
1847 ." but with a matching VMID from all enabled storages.",
1859 my $rpcenv = PVE
::RPCEnvironment
::get
();
1860 my $authuser = $rpcenv->get_user();
1861 my $vmid = $param->{vmid
};
1863 my $skiplock = $param->{skiplock
};
1864 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1865 if $skiplock && $authuser ne 'root@pam';
1867 my $early_checks = sub {
1869 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1870 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1872 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1874 if (!$param->{purge
}) {
1875 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1877 # don't allow destroy if with replication jobs but no purge param
1878 my $repl_conf = PVE
::ReplicationConfig-
>new();
1879 $repl_conf->check_for_existing_jobs($vmid);
1882 die "VM $vmid is running - destroy failed\n"
1883 if PVE
::QemuServer
::check_running
($vmid);
1893 my $storecfg = PVE
::Storage
::config
();
1895 syslog
('info', "destroy VM $vmid: $upid\n");
1896 PVE
::QemuConfig-
>lock_config($vmid, sub {
1897 # repeat, config might have changed
1898 my $ha_managed = $early_checks->();
1900 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1902 PVE
::QemuServer
::destroy_vm
(
1905 $skiplock, { lock => 'destroyed' },
1906 $purge_unreferenced,
1909 PVE
::AccessControl
::remove_vm_access
($vmid);
1910 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1911 if ($param->{purge
}) {
1912 print "purging VM $vmid from related configurations..\n";
1913 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1914 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1917 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1918 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1922 # only now remove the zombie config, else we can have reuse race
1923 PVE
::QemuConfig-
>destroy_config($vmid);
1927 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1930 __PACKAGE__-
>register_method({
1932 path
=> '{vmid}/unlink',
1936 description
=> "Unlink/delete disk images.",
1938 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1941 additionalProperties
=> 0,
1943 node
=> get_standard_option
('pve-node'),
1944 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1946 type
=> 'string', format
=> 'pve-configid-list',
1947 description
=> "A list of disk IDs you want to delete.",
1951 description
=> $opt_force_description,
1956 returns
=> { type
=> 'null'},
1960 $param->{delete} = extract_param
($param, 'idlist');
1962 __PACKAGE__-
>update_vm($param);
1967 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1968 my $gen_rand_chars = sub {
1971 die "invalid length $length" if $length < 1;
1973 my $min = ord('!'); # first printable ascii
1975 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1976 die "failed to generate random bytes!\n"
1979 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1986 __PACKAGE__-
>register_method({
1988 path
=> '{vmid}/vncproxy',
1992 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1994 description
=> "Creates a TCP VNC proxy connections.",
1996 additionalProperties
=> 0,
1998 node
=> get_standard_option
('pve-node'),
1999 vmid
=> get_standard_option
('pve-vmid'),
2003 description
=> "starts websockify instead of vncproxy",
2005 'generate-password' => {
2009 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
2014 additionalProperties
=> 0,
2016 user
=> { type
=> 'string' },
2017 ticket
=> { type
=> 'string' },
2020 description
=> "Returned if requested with 'generate-password' param."
2021 ." Consists of printable ASCII characters ('!' .. '~').",
2024 cert
=> { type
=> 'string' },
2025 port
=> { type
=> 'integer' },
2026 upid
=> { type
=> 'string' },
2032 my $rpcenv = PVE
::RPCEnvironment
::get
();
2034 my $authuser = $rpcenv->get_user();
2036 my $vmid = $param->{vmid
};
2037 my $node = $param->{node
};
2038 my $websocket = $param->{websocket
};
2040 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2044 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2045 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2048 my $authpath = "/vms/$vmid";
2050 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2051 my $password = $ticket;
2052 if ($param->{'generate-password'}) {
2053 $password = $gen_rand_chars->(8);
2056 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
2062 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2063 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2064 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2065 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
2066 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
2068 $family = PVE
::Tools
::get_host_address_family
($node);
2071 my $port = PVE
::Tools
::next_vnc_port
($family);
2078 syslog
('info', "starting vnc proxy $upid\n");
2082 if (defined($serial)) {
2084 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
2086 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
2087 '-timeout', $timeout, '-authpath', $authpath,
2088 '-perm', 'Sys.Console'];
2090 if ($param->{websocket
}) {
2091 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
2092 push @$cmd, '-notls', '-listen', 'localhost';
2095 push @$cmd, '-c', @$remcmd, @$termcmd;
2097 PVE
::Tools
::run_command
($cmd);
2101 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
2103 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
2105 my $sock = IO
::Socket
::IP-
>new(
2110 GetAddrInfoFlags
=> 0,
2111 ) or die "failed to create socket: $!\n";
2112 # Inside the worker we shouldn't have any previous alarms
2113 # running anyway...:
2115 local $SIG{ALRM
} = sub { die "connection timed out\n" };
2117 accept(my $cli, $sock) or die "connection failed: $!\n";
2120 if (PVE
::Tools
::run_command
($cmd,
2121 output
=> '>&'.fileno($cli),
2122 input
=> '<&'.fileno($cli),
2125 die "Failed to run vncproxy.\n";
2132 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2134 PVE
::Tools
::wait_for_vnc_port
($port);
2143 $res->{password
} = $password if $param->{'generate-password'};
2148 __PACKAGE__-
>register_method({
2149 name
=> 'termproxy',
2150 path
=> '{vmid}/termproxy',
2154 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2156 description
=> "Creates a TCP proxy connections.",
2158 additionalProperties
=> 0,
2160 node
=> get_standard_option
('pve-node'),
2161 vmid
=> get_standard_option
('pve-vmid'),
2165 enum
=> [qw(serial0 serial1 serial2 serial3)],
2166 description
=> "opens a serial terminal (defaults to display)",
2171 additionalProperties
=> 0,
2173 user
=> { type
=> 'string' },
2174 ticket
=> { type
=> 'string' },
2175 port
=> { type
=> 'integer' },
2176 upid
=> { type
=> 'string' },
2182 my $rpcenv = PVE
::RPCEnvironment
::get
();
2184 my $authuser = $rpcenv->get_user();
2186 my $vmid = $param->{vmid
};
2187 my $node = $param->{node
};
2188 my $serial = $param->{serial
};
2190 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
2192 if (!defined($serial)) {
2194 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2195 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
2199 my $authpath = "/vms/$vmid";
2201 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
2206 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
2207 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
2208 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
2209 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
2210 push @$remcmd, '--';
2212 $family = PVE
::Tools
::get_host_address_family
($node);
2215 my $port = PVE
::Tools
::next_vnc_port
($family);
2217 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2218 push @$termcmd, '-iface', $serial if $serial;
2223 syslog
('info', "starting qemu termproxy $upid\n");
2225 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2226 '--perm', 'VM.Console', '--'];
2227 push @$cmd, @$remcmd, @$termcmd;
2229 PVE
::Tools
::run_command
($cmd);
2232 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2234 PVE
::Tools
::wait_for_vnc_port
($port);
2244 __PACKAGE__-
>register_method({
2245 name
=> 'vncwebsocket',
2246 path
=> '{vmid}/vncwebsocket',
2249 description
=> "You also need to pass a valid ticket (vncticket).",
2250 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2252 description
=> "Opens a weksocket for VNC traffic.",
2254 additionalProperties
=> 0,
2256 node
=> get_standard_option
('pve-node'),
2257 vmid
=> get_standard_option
('pve-vmid'),
2259 description
=> "Ticket from previous call to vncproxy.",
2264 description
=> "Port number returned by previous vncproxy call.",
2274 port
=> { type
=> 'string' },
2280 my $rpcenv = PVE
::RPCEnvironment
::get
();
2282 my $authuser = $rpcenv->get_user();
2284 my $vmid = $param->{vmid
};
2285 my $node = $param->{node
};
2287 my $authpath = "/vms/$vmid";
2289 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2291 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2293 # Note: VNC ports are acessible from outside, so we do not gain any
2294 # security if we verify that $param->{port} belongs to VM $vmid. This
2295 # check is done by verifying the VNC ticket (inside VNC protocol).
2297 my $port = $param->{port
};
2299 return { port
=> $port };
2302 __PACKAGE__-
>register_method({
2303 name
=> 'spiceproxy',
2304 path
=> '{vmid}/spiceproxy',
2309 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2311 description
=> "Returns a SPICE configuration to connect to the VM.",
2313 additionalProperties
=> 0,
2315 node
=> get_standard_option
('pve-node'),
2316 vmid
=> get_standard_option
('pve-vmid'),
2317 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2320 returns
=> get_standard_option
('remote-viewer-config'),
2324 my $rpcenv = PVE
::RPCEnvironment
::get
();
2326 my $authuser = $rpcenv->get_user();
2328 my $vmid = $param->{vmid
};
2329 my $node = $param->{node
};
2330 my $proxy = $param->{proxy
};
2332 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2333 my $title = "VM $vmid";
2334 $title .= " - ". $conf->{name
} if $conf->{name
};
2336 my $port = PVE
::QemuServer
::spice_port
($vmid);
2338 my ($ticket, undef, $remote_viewer_config) =
2339 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2341 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2342 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2344 return $remote_viewer_config;
2347 __PACKAGE__-
>register_method({
2349 path
=> '{vmid}/status',
2352 description
=> "Directory index",
2357 additionalProperties
=> 0,
2359 node
=> get_standard_option
('pve-node'),
2360 vmid
=> get_standard_option
('pve-vmid'),
2368 subdir
=> { type
=> 'string' },
2371 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2377 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2380 { subdir
=> 'current' },
2381 { subdir
=> 'start' },
2382 { subdir
=> 'stop' },
2383 { subdir
=> 'reset' },
2384 { subdir
=> 'shutdown' },
2385 { subdir
=> 'suspend' },
2386 { subdir
=> 'reboot' },
2392 __PACKAGE__-
>register_method({
2393 name
=> 'vm_status',
2394 path
=> '{vmid}/status/current',
2397 protected
=> 1, # qemu pid files are only readable by root
2398 description
=> "Get virtual machine status.",
2400 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2403 additionalProperties
=> 0,
2405 node
=> get_standard_option
('pve-node'),
2406 vmid
=> get_standard_option
('pve-vmid'),
2412 %$PVE::QemuServer
::vmstatus_return_properties
,
2414 description
=> "HA manager service status.",
2418 description
=> "Qemu VGA configuration supports spice.",
2423 description
=> "Qemu GuestAgent enabled in config.",
2433 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2435 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2436 my $status = $vmstatus->{$param->{vmid
}};
2438 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2441 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
2442 $status->{spice
} = 1
2443 if $vga->{type
} eq 'virtio-gl' || PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2445 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2450 __PACKAGE__-
>register_method({
2452 path
=> '{vmid}/status/start',
2456 description
=> "Start virtual machine.",
2458 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2461 additionalProperties
=> 0,
2463 node
=> get_standard_option
('pve-node'),
2464 vmid
=> get_standard_option
('pve-vmid',
2465 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2466 skiplock
=> get_standard_option
('skiplock'),
2467 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2468 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2471 enum
=> ['secure', 'insecure'],
2472 description
=> "Migration traffic is encrypted using an SSH " .
2473 "tunnel by default. On secure, completely private networks " .
2474 "this can be disabled to increase performance.",
2477 migration_network
=> {
2478 type
=> 'string', format
=> 'CIDR',
2479 description
=> "CIDR of the (sub) network that is used for migration.",
2482 machine
=> get_standard_option
('pve-qemu-machine'),
2484 description
=> "Override QEMU's -cpu argument with the given string.",
2488 targetstorage
=> get_standard_option
('pve-targetstorage'),
2490 description
=> "Wait maximal timeout seconds.",
2493 default => 'max(30, vm memory in GiB)',
2504 my $rpcenv = PVE
::RPCEnvironment
::get
();
2505 my $authuser = $rpcenv->get_user();
2507 my $node = extract_param
($param, 'node');
2508 my $vmid = extract_param
($param, 'vmid');
2509 my $timeout = extract_param
($param, 'timeout');
2510 my $machine = extract_param
($param, 'machine');
2512 my $get_root_param = sub {
2513 my $value = extract_param
($param, $_[0]);
2514 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2515 if $value && $authuser ne 'root@pam';
2519 my $stateuri = $get_root_param->('stateuri');
2520 my $skiplock = $get_root_param->('skiplock');
2521 my $migratedfrom = $get_root_param->('migratedfrom');
2522 my $migration_type = $get_root_param->('migration_type');
2523 my $migration_network = $get_root_param->('migration_network');
2524 my $targetstorage = $get_root_param->('targetstorage');
2525 my $force_cpu = $get_root_param->('force-cpu');
2529 if ($targetstorage) {
2530 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2532 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2533 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2537 # read spice ticket from STDIN
2539 my $nbd_protocol_version = 0;
2540 my $replicated_volumes = {};
2542 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2543 while (defined(my $line = <STDIN
>)) {
2545 if ($line =~ m/^spice_ticket: (.+)$/) {
2547 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2548 $nbd_protocol_version = $1;
2549 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2550 $replicated_volumes->{$1} = 1;
2551 } elsif ($line =~ m/^tpmstate0: (.*)$/) {
2553 } elsif (!$spice_ticket) {
2554 # fallback for old source node
2555 $spice_ticket = $line;
2557 warn "unknown 'start' parameter on STDIN: '$line'\n";
2562 PVE
::Cluster
::check_cfs_quorum
();
2564 my $storecfg = PVE
::Storage
::config
();
2566 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2570 print "Requesting HA start for VM $vmid\n";
2572 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2573 PVE
::Tools
::run_command
($cmd);
2577 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2584 syslog
('info', "start VM $vmid: $upid\n");
2586 my $migrate_opts = {
2587 migratedfrom
=> $migratedfrom,
2588 spice_ticket
=> $spice_ticket,
2589 network
=> $migration_network,
2590 type
=> $migration_type,
2591 storagemap
=> $storagemap,
2592 nbd_proto_version
=> $nbd_protocol_version,
2593 replicated_volumes
=> $replicated_volumes,
2594 tpmstate_vol
=> $tpmstate_vol,
2598 statefile
=> $stateuri,
2599 skiplock
=> $skiplock,
2600 forcemachine
=> $machine,
2601 timeout
=> $timeout,
2602 forcecpu
=> $force_cpu,
2605 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2609 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2613 __PACKAGE__-
>register_method({
2615 path
=> '{vmid}/status/stop',
2619 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2620 "is akin to pulling the power plug of a running computer and may damage the VM data",
2622 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2625 additionalProperties
=> 0,
2627 node
=> get_standard_option
('pve-node'),
2628 vmid
=> get_standard_option
('pve-vmid',
2629 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2630 skiplock
=> get_standard_option
('skiplock'),
2631 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2633 description
=> "Wait maximal timeout seconds.",
2639 description
=> "Do not deactivate storage volumes.",
2652 my $rpcenv = PVE
::RPCEnvironment
::get
();
2653 my $authuser = $rpcenv->get_user();
2655 my $node = extract_param
($param, 'node');
2656 my $vmid = extract_param
($param, 'vmid');
2658 my $skiplock = extract_param
($param, 'skiplock');
2659 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2660 if $skiplock && $authuser ne 'root@pam';
2662 my $keepActive = extract_param
($param, 'keepActive');
2663 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2664 if $keepActive && $authuser ne 'root@pam';
2666 my $migratedfrom = extract_param
($param, 'migratedfrom');
2667 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2668 if $migratedfrom && $authuser ne 'root@pam';
2671 my $storecfg = PVE
::Storage
::config
();
2673 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2678 print "Requesting HA stop for VM $vmid\n";
2680 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2681 PVE
::Tools
::run_command
($cmd);
2685 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2691 syslog
('info', "stop VM $vmid: $upid\n");
2693 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2694 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2698 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2702 __PACKAGE__-
>register_method({
2704 path
=> '{vmid}/status/reset',
2708 description
=> "Reset virtual machine.",
2710 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2713 additionalProperties
=> 0,
2715 node
=> get_standard_option
('pve-node'),
2716 vmid
=> get_standard_option
('pve-vmid',
2717 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2718 skiplock
=> get_standard_option
('skiplock'),
2727 my $rpcenv = PVE
::RPCEnvironment
::get
();
2729 my $authuser = $rpcenv->get_user();
2731 my $node = extract_param
($param, 'node');
2733 my $vmid = extract_param
($param, 'vmid');
2735 my $skiplock = extract_param
($param, 'skiplock');
2736 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2737 if $skiplock && $authuser ne 'root@pam';
2739 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2744 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2749 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2752 __PACKAGE__-
>register_method({
2753 name
=> 'vm_shutdown',
2754 path
=> '{vmid}/status/shutdown',
2758 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2759 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2761 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2764 additionalProperties
=> 0,
2766 node
=> get_standard_option
('pve-node'),
2767 vmid
=> get_standard_option
('pve-vmid',
2768 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2769 skiplock
=> get_standard_option
('skiplock'),
2771 description
=> "Wait maximal timeout seconds.",
2777 description
=> "Make sure the VM stops.",
2783 description
=> "Do not deactivate storage volumes.",
2796 my $rpcenv = PVE
::RPCEnvironment
::get
();
2797 my $authuser = $rpcenv->get_user();
2799 my $node = extract_param
($param, 'node');
2800 my $vmid = extract_param
($param, 'vmid');
2802 my $skiplock = extract_param
($param, 'skiplock');
2803 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2804 if $skiplock && $authuser ne 'root@pam';
2806 my $keepActive = extract_param
($param, 'keepActive');
2807 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2808 if $keepActive && $authuser ne 'root@pam';
2810 my $storecfg = PVE
::Storage
::config
();
2814 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2815 # otherwise, we will infer a shutdown command, but run into the timeout,
2816 # then when the vm is resumed, it will instantly shutdown
2818 # checking the qmp status here to get feedback to the gui/cli/api
2819 # and the status query should not take too long
2820 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2821 if ($param->{forceStop
}) {
2822 warn "VM is paused - stop instead of shutdown\n";
2825 die "VM is paused - cannot shutdown\n";
2829 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2831 my $timeout = $param->{timeout
} // 60;
2835 print "Requesting HA stop for VM $vmid\n";
2837 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2838 PVE
::Tools
::run_command
($cmd);
2842 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2849 syslog
('info', "shutdown VM $vmid: $upid\n");
2851 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2852 $shutdown, $param->{forceStop
}, $keepActive);
2856 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2860 __PACKAGE__-
>register_method({
2861 name
=> 'vm_reboot',
2862 path
=> '{vmid}/status/reboot',
2866 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2868 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2871 additionalProperties
=> 0,
2873 node
=> get_standard_option
('pve-node'),
2874 vmid
=> get_standard_option
('pve-vmid',
2875 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2877 description
=> "Wait maximal timeout seconds for the shutdown.",
2890 my $rpcenv = PVE
::RPCEnvironment
::get
();
2891 my $authuser = $rpcenv->get_user();
2893 my $node = extract_param
($param, 'node');
2894 my $vmid = extract_param
($param, 'vmid');
2896 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2898 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2903 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2904 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2908 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2911 __PACKAGE__-
>register_method({
2912 name
=> 'vm_suspend',
2913 path
=> '{vmid}/status/suspend',
2917 description
=> "Suspend virtual machine.",
2919 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2920 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2921 " on the storage for the vmstate.",
2922 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2925 additionalProperties
=> 0,
2927 node
=> get_standard_option
('pve-node'),
2928 vmid
=> get_standard_option
('pve-vmid',
2929 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2930 skiplock
=> get_standard_option
('skiplock'),
2935 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2937 statestorage
=> get_standard_option
('pve-storage-id', {
2938 description
=> "The storage for the VM state",
2939 requires
=> 'todisk',
2941 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2951 my $rpcenv = PVE
::RPCEnvironment
::get
();
2952 my $authuser = $rpcenv->get_user();
2954 my $node = extract_param
($param, 'node');
2955 my $vmid = extract_param
($param, 'vmid');
2957 my $todisk = extract_param
($param, 'todisk') // 0;
2959 my $statestorage = extract_param
($param, 'statestorage');
2961 my $skiplock = extract_param
($param, 'skiplock');
2962 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2963 if $skiplock && $authuser ne 'root@pam';
2965 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2967 die "Cannot suspend HA managed VM to disk\n"
2968 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2970 # early check for storage permission, for better user feedback
2972 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2974 if (!$statestorage) {
2975 # get statestorage from config if none is given
2976 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2977 my $storecfg = PVE
::Storage
::config
();
2978 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2981 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2987 syslog
('info', "suspend VM $vmid: $upid\n");
2989 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2994 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2995 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2998 __PACKAGE__-
>register_method({
2999 name
=> 'vm_resume',
3000 path
=> '{vmid}/status/resume',
3004 description
=> "Resume virtual machine.",
3006 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
3009 additionalProperties
=> 0,
3011 node
=> get_standard_option
('pve-node'),
3012 vmid
=> get_standard_option
('pve-vmid',
3013 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3014 skiplock
=> get_standard_option
('skiplock'),
3015 nocheck
=> { type
=> 'boolean', optional
=> 1 },
3025 my $rpcenv = PVE
::RPCEnvironment
::get
();
3027 my $authuser = $rpcenv->get_user();
3029 my $node = extract_param
($param, 'node');
3031 my $vmid = extract_param
($param, 'vmid');
3033 my $skiplock = extract_param
($param, 'skiplock');
3034 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3035 if $skiplock && $authuser ne 'root@pam';
3037 my $nocheck = extract_param
($param, 'nocheck');
3038 raise_param_exc
({ nocheck
=> "Only root may use this option." })
3039 if $nocheck && $authuser ne 'root@pam';
3041 my $to_disk_suspended;
3043 PVE
::QemuConfig-
>lock_config($vmid, sub {
3044 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3045 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
3049 die "VM $vmid not running\n"
3050 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
3055 syslog
('info', "resume VM $vmid: $upid\n");
3057 if (!$to_disk_suspended) {
3058 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
3060 my $storecfg = PVE
::Storage
::config
();
3061 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
3067 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
3070 __PACKAGE__-
>register_method({
3071 name
=> 'vm_sendkey',
3072 path
=> '{vmid}/sendkey',
3076 description
=> "Send key event to virtual machine.",
3078 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
3081 additionalProperties
=> 0,
3083 node
=> get_standard_option
('pve-node'),
3084 vmid
=> get_standard_option
('pve-vmid',
3085 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3086 skiplock
=> get_standard_option
('skiplock'),
3088 description
=> "The key (qemu monitor encoding).",
3093 returns
=> { type
=> 'null'},
3097 my $rpcenv = PVE
::RPCEnvironment
::get
();
3099 my $authuser = $rpcenv->get_user();
3101 my $node = extract_param
($param, 'node');
3103 my $vmid = extract_param
($param, 'vmid');
3105 my $skiplock = extract_param
($param, 'skiplock');
3106 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3107 if $skiplock && $authuser ne 'root@pam';
3109 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
3114 __PACKAGE__-
>register_method({
3115 name
=> 'vm_feature',
3116 path
=> '{vmid}/feature',
3120 description
=> "Check if feature for virtual machine is available.",
3122 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3125 additionalProperties
=> 0,
3127 node
=> get_standard_option
('pve-node'),
3128 vmid
=> get_standard_option
('pve-vmid'),
3130 description
=> "Feature to check.",
3132 enum
=> [ 'snapshot', 'clone', 'copy' ],
3134 snapname
=> get_standard_option
('pve-snapshot-name', {
3142 hasFeature
=> { type
=> 'boolean' },
3145 items
=> { type
=> 'string' },
3152 my $node = extract_param
($param, 'node');
3154 my $vmid = extract_param
($param, 'vmid');
3156 my $snapname = extract_param
($param, 'snapname');
3158 my $feature = extract_param
($param, 'feature');
3160 my $running = PVE
::QemuServer
::check_running
($vmid);
3162 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3165 my $snap = $conf->{snapshots
}->{$snapname};
3166 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3169 my $storecfg = PVE
::Storage
::config
();
3171 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
3172 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
3175 hasFeature
=> $hasFeature,
3176 nodes
=> [ keys %$nodelist ],
3180 __PACKAGE__-
>register_method({
3182 path
=> '{vmid}/clone',
3186 description
=> "Create a copy of virtual machine/template.",
3188 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
3189 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
3190 "'Datastore.AllocateSpace' on any used storage.",
3193 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
3195 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
3196 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
3201 additionalProperties
=> 0,
3203 node
=> get_standard_option
('pve-node'),
3204 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3205 newid
=> get_standard_option
('pve-vmid', {
3206 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
3207 description
=> 'VMID for the clone.' }),
3210 type
=> 'string', format
=> 'dns-name',
3211 description
=> "Set a name for the new VM.",
3216 description
=> "Description for the new VM.",
3220 type
=> 'string', format
=> 'pve-poolid',
3221 description
=> "Add the new VM to the specified pool.",
3223 snapname
=> get_standard_option
('pve-snapshot-name', {
3226 storage
=> get_standard_option
('pve-storage-id', {
3227 description
=> "Target storage for full clone.",
3231 description
=> "Target format for file storage. Only valid for full clone.",
3234 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3239 description
=> "Create a full copy of all disks. This is always done when " .
3240 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3242 target
=> get_standard_option
('pve-node', {
3243 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3247 description
=> "Override I/O bandwidth limit (in KiB/s).",
3251 default => 'clone limit from datacenter or storage config',
3261 my $rpcenv = PVE
::RPCEnvironment
::get
();
3262 my $authuser = $rpcenv->get_user();
3264 my $node = extract_param
($param, 'node');
3265 my $vmid = extract_param
($param, 'vmid');
3266 my $newid = extract_param
($param, 'newid');
3267 my $pool = extract_param
($param, 'pool');
3269 my $snapname = extract_param
($param, 'snapname');
3270 my $storage = extract_param
($param, 'storage');
3271 my $format = extract_param
($param, 'format');
3272 my $target = extract_param
($param, 'target');
3274 my $localnode = PVE
::INotify
::nodename
();
3276 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3280 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3282 my $load_and_check = sub {
3283 $rpcenv->check_pool_exist($pool) if defined($pool);
3284 PVE
::Cluster
::check_node_exists
($target) if $target;
3286 my $storecfg = PVE
::Storage
::config
();
3289 # check if storage is enabled on local node
3290 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3292 # check if storage is available on target node
3293 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3294 # clone only works if target storage is shared
3295 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3296 die "can't clone to non-shared storage '$storage'\n"
3297 if !$scfg->{shared
};
3301 PVE
::Cluster
::check_cfs_quorum
();
3303 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3304 PVE
::QemuConfig-
>check_lock($conf);
3306 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3307 die "unexpected state change\n" if $verify_running != $running;
3309 die "snapshot '$snapname' does not exist\n"
3310 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3312 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3314 die "parameter 'storage' not allowed for linked clones\n"
3315 if defined($storage) && !$full;
3317 die "parameter 'format' not allowed for linked clones\n"
3318 if defined($format) && !$full;
3320 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3322 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3324 die "can't clone VM to node '$target' (VM uses local storage)\n"
3325 if $target && !$sharedvm;
3327 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3328 die "unable to create VM $newid: config file already exists\n"
3331 my $newconf = { lock => 'clone' };
3336 foreach my $opt (keys %$oldconf) {
3337 my $value = $oldconf->{$opt};
3339 # do not copy snapshot related info
3340 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3341 $opt eq 'vmstate' || $opt eq 'snapstate';
3343 # no need to copy unused images, because VMID(owner) changes anyways
3344 next if $opt =~ m/^unused\d+$/;
3346 die "cannot clone TPM state while VM is running\n"
3347 if $full && $running && !$snapname && $opt eq 'tpmstate0';
3349 # always change MAC! address
3350 if ($opt =~ m/^net(\d+)$/) {
3351 my $net = PVE
::QemuServer
::parse_net
($value);
3352 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3353 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3354 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3355 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3356 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3357 die "unable to parse drive options for '$opt'\n" if !$drive;
3358 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3359 $newconf->{$opt} = $value; # simply copy configuration
3361 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3362 die "Full clone feature is not supported for drive '$opt'\n"
3363 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3364 $fullclone->{$opt} = 1;
3366 # not full means clone instead of copy
3367 die "Linked clone feature is not supported for drive '$opt'\n"
3368 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3370 $drives->{$opt} = $drive;
3371 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3372 push @$vollist, $drive->{file
};
3375 # copy everything else
3376 $newconf->{$opt} = $value;
3380 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3384 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3385 my $storecfg = PVE
::Storage
::config
();
3387 # auto generate a new uuid
3388 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3389 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3390 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3391 # auto generate a new vmgenid only if the option was set for template
3392 if ($newconf->{vmgenid
}) {
3393 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3396 delete $newconf->{template
};
3398 if ($param->{name
}) {
3399 $newconf->{name
} = $param->{name
};
3401 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3404 if ($param->{description
}) {
3405 $newconf->{description
} = $param->{description
};
3408 # create empty/temp config - this fails if VM already exists on other node
3409 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3410 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3412 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3414 my $newvollist = [];
3421 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3423 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3425 my $bwlimit = extract_param
($param, 'bwlimit');
3427 my $total_jobs = scalar(keys %{$drives});
3430 foreach my $opt (sort keys %$drives) {
3431 my $drive = $drives->{$opt};
3432 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3433 my $completion = $skipcomplete ?
'skip' : 'complete';
3435 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3436 my $storage_list = [ $src_sid ];
3437 push @$storage_list, $storage if defined($storage);
3438 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3442 running
=> $running,
3445 snapname
=> $snapname,
3451 storage
=> $storage,
3455 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($oldconf)
3456 if $opt eq 'efidisk0';
3458 my $newdrive = PVE
::QemuServer
::clone_disk
(
3470 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3472 PVE
::QemuConfig-
>write_config($newid, $newconf);
3476 delete $newconf->{lock};
3478 # do not write pending changes
3479 if (my @changes = keys %{$newconf->{pending
}}) {
3480 my $pending = join(',', @changes);
3481 warn "found pending changes for '$pending', discarding for clone\n";
3482 delete $newconf->{pending
};
3485 PVE
::QemuConfig-
>write_config($newid, $newconf);
3488 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3489 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3490 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3492 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3493 die "Failed to move config to node '$target' - rename failed: $!\n"
3494 if !rename($conffile, $newconffile);
3497 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3500 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3501 sleep 1; # some storage like rbd need to wait before release volume - really?
3503 foreach my $volid (@$newvollist) {
3504 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3508 PVE
::Firewall
::remove_vmfw_conf
($newid);
3510 unlink $conffile; # avoid races -> last thing before die
3512 die "clone failed: $err";
3518 # Aquire exclusive lock lock for $newid
3519 my $lock_target_vm = sub {
3520 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3523 my $lock_source_vm = sub {
3524 # exclusive lock if VM is running - else shared lock is enough;
3526 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3528 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3532 $load_and_check->(); # early checks before forking/locking
3534 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3537 __PACKAGE__-
>register_method({
3538 name
=> 'move_vm_disk',
3539 path
=> '{vmid}/move_disk',
3543 description
=> "Move volume to different storage or to a different VM.",
3545 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3546 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3547 "a disk to another VM, you need the permissions on the target VM as well.",
3548 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3551 additionalProperties
=> 0,
3553 node
=> get_standard_option
('pve-node'),
3554 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3555 'target-vmid' => get_standard_option
('pve-vmid', {
3556 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3561 description
=> "The disk you want to move.",
3562 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3564 storage
=> get_standard_option
('pve-storage-id', {
3565 description
=> "Target storage.",
3566 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3571 description
=> "Target Format.",
3572 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3577 description
=> "Delete the original disk after successful copy. By default the"
3578 ." original disk is kept as unused disk.",
3584 description
=> 'Prevent changes if current configuration file has different SHA1"
3585 ." digest. This can be used to prevent concurrent modifications.',
3590 description
=> "Override I/O bandwidth limit (in KiB/s).",
3594 default => 'move limit from datacenter or storage config',
3598 description
=> "The config key the disk will be moved to on the target VM"
3599 ." (for example, ide0 or scsi1). Default is the source disk key.",
3600 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3603 'target-digest' => {
3605 description
=> 'Prevent changes if the current config file of the target VM has a"
3606 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3614 description
=> "the task ID.",
3619 my $rpcenv = PVE
::RPCEnvironment
::get
();
3620 my $authuser = $rpcenv->get_user();
3622 my $node = extract_param
($param, 'node');
3623 my $vmid = extract_param
($param, 'vmid');
3624 my $target_vmid = extract_param
($param, 'target-vmid');
3625 my $digest = extract_param
($param, 'digest');
3626 my $target_digest = extract_param
($param, 'target-digest');
3627 my $disk = extract_param
($param, 'disk');
3628 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3629 my $storeid = extract_param
($param, 'storage');
3630 my $format = extract_param
($param, 'format');
3632 my $storecfg = PVE
::Storage
::config
();
3634 my $load_and_check_move = sub {
3635 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3636 PVE
::QemuConfig-
>check_lock($conf);
3638 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3640 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3642 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3644 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3645 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3647 my $old_volid = $drive->{file
};
3649 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3650 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3654 die "you can't move to the same storage with same format\n"
3655 if $oldstoreid eq $storeid && (!$format || !$oldfmt || $oldfmt eq $format);
3657 # this only checks snapshots because $disk is passed!
3658 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3664 die "you can't move a disk with snapshots and delete the source\n"
3665 if $snapshotted && $param->{delete};
3667 return ($conf, $drive, $oldstoreid, $snapshotted);
3670 my $move_updatefn = sub {
3671 my ($conf, $drive, $oldstoreid, $snapshotted) = $load_and_check_move->();
3672 my $old_volid = $drive->{file
};
3674 PVE
::Cluster
::log_msg
(
3677 "move disk VM $vmid: move --disk $disk --storage $storeid"
3680 my $running = PVE
::QemuServer
::check_running
($vmid);
3682 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3684 my $newvollist = [];
3690 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3692 warn "moving disk with snapshots, snapshots will not be moved!\n"
3695 my $bwlimit = extract_param
($param, 'bwlimit');
3696 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3698 [$oldstoreid, $storeid],
3704 running
=> $running,
3713 storage
=> $storeid,
3717 $dest_info->{efisize
} = PVE
::QemuServer
::get_efivars_size
($conf)
3718 if $disk eq 'efidisk0';
3720 my $newdrive = PVE
::QemuServer
::clone_disk
(
3731 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3733 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3735 # convert moved disk to base if part of template
3736 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3737 if PVE
::QemuConfig-
>is_template($conf);
3739 PVE
::QemuConfig-
>write_config($vmid, $conf);
3741 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3742 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3743 eval { mon_cmd
($vmid, "guest-fstrim") };
3747 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3748 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3754 foreach my $volid (@$newvollist) {
3755 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3758 die "storage migration failed: $err";
3761 if ($param->{delete}) {
3763 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3764 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3770 my $load_and_check_reassign_configs = sub {
3771 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
3773 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
3774 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
3776 my $source_node = $vmlist->{$vmid}->{node
};
3777 my $target_node = $vmlist->{$target_vmid}->{node
};
3779 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
3780 if $source_node ne $target_node;
3782 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
3783 PVE
::QemuConfig-
>check_lock($source_conf);
3784 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
3785 PVE
::QemuConfig-
>check_lock($target_conf);
3787 die "Can't move disks from or to template VMs\n"
3788 if ($source_conf->{template
} || $target_conf->{template
});
3791 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
3792 die "VM ${vmid}: $@" if $@;
3795 if ($target_digest) {
3796 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
3797 die "VM ${target_vmid}: $@" if $@;
3800 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
3802 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
3803 if $target_conf->{$target_disk};
3805 my $drive = PVE
::QemuServer
::parse_drive
(
3807 $source_conf->{$disk},
3809 die "failed to parse source disk - $@\n" if !$drive;
3811 my $source_volid = $drive->{file
};
3813 die "disk '${disk}' has no associated volume\n" if !$source_volid;
3814 die "CD drive contents can't be moved to another VM\n"
3815 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3817 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
3818 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
3820 die "Can't move disk used by a snapshot to another VM\n"
3821 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
3822 die "Storage does not support moving of this disk to another VM\n"
3823 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
3824 die "Cannot move disk to another VM while the source VM is running - detach first\n"
3825 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
3827 # now re-parse using target disk slot format
3828 if ($target_disk =~ /^unused\d+$/) {
3829 $drive = PVE
::QemuServer
::parse_drive
(
3834 $drive = PVE
::QemuServer
::parse_drive
(
3836 $source_conf->{$disk},
3839 die "failed to parse source disk for target disk format - $@\n" if !$drive;
3841 my $repl_conf = PVE
::ReplicationConfig-
>new();
3842 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
3843 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3844 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
3845 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
3848 return ($source_conf, $target_conf, $drive);
3853 print STDERR
"$msg\n";
3856 my $disk_reassignfn = sub {
3857 return PVE
::QemuConfig-
>lock_config($vmid, sub {
3858 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
3859 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
3861 my $source_volid = $drive->{file
};
3863 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
3864 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
3866 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3868 my $new_volid = PVE
::Storage
::rename_volume
(
3874 $drive->{file
} = $new_volid;
3876 delete $source_conf->{$disk};
3877 print "removing disk '${disk}' from VM '${vmid}' config\n";
3878 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
3880 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
3882 if ($target_disk =~ /^unused\d+$/) {
3883 $target_conf->{$target_disk} = $drive_string;
3884 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
3889 vmid
=> $target_vmid,
3890 digest
=> $target_digest,
3891 $target_disk => $drive_string,
3897 # remove possible replication snapshots
3898 if (PVE
::Storage
::volume_has_feature
(
3904 PVE
::Replication
::prepare
(
3914 print "Failed to remove replication snapshots on moved disk " .
3915 "'$target_disk'. Manual cleanup could be necessary.\n";
3922 if ($target_vmid && $storeid) {
3923 my $msg = "either set 'storage' or 'target-vmid', but not both";
3924 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3925 } elsif ($target_vmid) {
3926 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
3927 if $authuser ne 'root@pam';
3929 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
3930 if $vmid eq $target_vmid;
3932 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
3933 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
3934 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
3936 return $rpcenv->fork_worker(
3938 "${vmid}-${disk}>${target_vmid}-${target_disk}",
3942 } elsif ($storeid) {
3943 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3945 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
3946 if $disk =~ m/^unused\d+$/;
3948 $load_and_check_move->(); # early checks before forking/locking
3951 PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
3954 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3956 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
3957 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3961 my $check_vm_disks_local = sub {
3962 my ($storecfg, $vmconf, $vmid) = @_;
3964 my $local_disks = {};
3966 # add some more information to the disks e.g. cdrom
3967 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3968 my ($volid, $attr) = @_;
3970 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3972 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3973 return if $scfg->{shared
};
3975 # The shared attr here is just a special case where the vdisk
3976 # is marked as shared manually
3977 return if $attr->{shared
};
3978 return if $attr->{cdrom
} and $volid eq "none";
3980 if (exists $local_disks->{$volid}) {
3981 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3983 $local_disks->{$volid} = $attr;
3984 # ensure volid is present in case it's needed
3985 $local_disks->{$volid}->{volid
} = $volid;
3989 return $local_disks;
3992 __PACKAGE__-
>register_method({
3993 name
=> 'migrate_vm_precondition',
3994 path
=> '{vmid}/migrate',
3998 description
=> "Get preconditions for migration.",
4000 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4003 additionalProperties
=> 0,
4005 node
=> get_standard_option
('pve-node'),
4006 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4007 target
=> get_standard_option
('pve-node', {
4008 description
=> "Target node.",
4009 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4017 running
=> { type
=> 'boolean' },
4021 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
4023 not_allowed_nodes
=> {
4026 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
4030 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
4032 local_resources
=> {
4034 description
=> "List local resources e.g. pci, usb"
4041 my $rpcenv = PVE
::RPCEnvironment
::get
();
4043 my $authuser = $rpcenv->get_user();
4045 PVE
::Cluster
::check_cfs_quorum
();
4049 my $vmid = extract_param
($param, 'vmid');
4050 my $target = extract_param
($param, 'target');
4051 my $localnode = PVE
::INotify
::nodename
();
4055 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
4056 my $storecfg = PVE
::Storage
::config
();
4059 # try to detect errors early
4060 PVE
::QemuConfig-
>check_lock($vmconf);
4062 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
4064 # if vm is not running, return target nodes where local storage is available
4065 # for offline migration
4066 if (!$res->{running
}) {
4067 $res->{allowed_nodes
} = [];
4068 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
4069 delete $checked_nodes->{$localnode};
4071 foreach my $node (keys %$checked_nodes) {
4072 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
4073 push @{$res->{allowed_nodes
}}, $node;
4077 $res->{not_allowed_nodes
} = $checked_nodes;
4081 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
4082 $res->{local_disks
} = [ values %$local_disks ];;
4084 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
4086 $res->{local_resources
} = $local_resources;
4093 __PACKAGE__-
>register_method({
4094 name
=> 'migrate_vm',
4095 path
=> '{vmid}/migrate',
4099 description
=> "Migrate virtual machine. Creates a new migration task.",
4101 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
4104 additionalProperties
=> 0,
4106 node
=> get_standard_option
('pve-node'),
4107 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4108 target
=> get_standard_option
('pve-node', {
4109 description
=> "Target node.",
4110 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
4114 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
4119 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
4124 enum
=> ['secure', 'insecure'],
4125 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
4128 migration_network
=> {
4129 type
=> 'string', format
=> 'CIDR',
4130 description
=> "CIDR of the (sub) network that is used for migration.",
4133 "with-local-disks" => {
4135 description
=> "Enable live storage migration for local disk",
4138 targetstorage
=> get_standard_option
('pve-targetstorage', {
4139 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
4142 description
=> "Override I/O bandwidth limit (in KiB/s).",
4146 default => 'migrate limit from datacenter or storage config',
4152 description
=> "the task ID.",
4157 my $rpcenv = PVE
::RPCEnvironment
::get
();
4158 my $authuser = $rpcenv->get_user();
4160 my $target = extract_param
($param, 'target');
4162 my $localnode = PVE
::INotify
::nodename
();
4163 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
4165 PVE
::Cluster
::check_cfs_quorum
();
4167 PVE
::Cluster
::check_node_exists
($target);
4169 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
4171 my $vmid = extract_param
($param, 'vmid');
4173 raise_param_exc
({ force
=> "Only root may use this option." })
4174 if $param->{force
} && $authuser ne 'root@pam';
4176 raise_param_exc
({ migration_type
=> "Only root may use this option." })
4177 if $param->{migration_type
} && $authuser ne 'root@pam';
4179 # allow root only until better network permissions are available
4180 raise_param_exc
({ migration_network
=> "Only root may use this option." })
4181 if $param->{migration_network
} && $authuser ne 'root@pam';
4184 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4186 # try to detect errors early
4188 PVE
::QemuConfig-
>check_lock($conf);
4190 if (PVE
::QemuServer
::check_running
($vmid)) {
4191 die "can't migrate running VM without --online\n" if !$param->{online
};
4193 my $repl_conf = PVE
::ReplicationConfig-
>new();
4194 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
4195 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
4196 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
4197 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
4198 "target. Use 'force' to override.\n";
4201 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
4202 $param->{online
} = 0;
4205 my $storecfg = PVE
::Storage
::config
();
4206 if (my $targetstorage = $param->{targetstorage
}) {
4207 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
4208 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
4211 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
4212 if !defined($storagemap->{identity
});
4214 foreach my $target_sid (values %{$storagemap->{entries
}}) {
4215 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
4218 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
4219 if $storagemap->{default};
4221 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
4222 if $storagemap->{identity
};
4224 $param->{storagemap
} = $storagemap;
4226 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
4229 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
4234 print "Requesting HA migration for VM $vmid to node $target\n";
4236 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
4237 PVE
::Tools
::run_command
($cmd);
4241 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
4246 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
4250 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4253 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4258 __PACKAGE__-
>register_method({
4260 path
=> '{vmid}/monitor',
4264 description
=> "Execute Qemu monitor commands.",
4266 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4267 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4270 additionalProperties
=> 0,
4272 node
=> get_standard_option
('pve-node'),
4273 vmid
=> get_standard_option
('pve-vmid'),
4276 description
=> "The monitor command.",
4280 returns
=> { type
=> 'string'},
4284 my $rpcenv = PVE
::RPCEnvironment
::get
();
4285 my $authuser = $rpcenv->get_user();
4288 my $command = shift;
4289 return $command =~ m/^\s*info(\s+|$)/
4290 || $command =~ m/^\s*help\s*$/;
4293 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4294 if !&$is_ro($param->{command
});
4296 my $vmid = $param->{vmid
};
4298 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4302 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4304 $res = "ERROR: $@" if $@;
4309 __PACKAGE__-
>register_method({
4310 name
=> 'resize_vm',
4311 path
=> '{vmid}/resize',
4315 description
=> "Extend volume size.",
4317 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4320 additionalProperties
=> 0,
4322 node
=> get_standard_option
('pve-node'),
4323 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4324 skiplock
=> get_standard_option
('skiplock'),
4327 description
=> "The disk you want to resize.",
4328 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4332 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4333 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.",
4337 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4343 returns
=> { type
=> 'null'},
4347 my $rpcenv = PVE
::RPCEnvironment
::get
();
4349 my $authuser = $rpcenv->get_user();
4351 my $node = extract_param
($param, 'node');
4353 my $vmid = extract_param
($param, 'vmid');
4355 my $digest = extract_param
($param, 'digest');
4357 my $disk = extract_param
($param, 'disk');
4359 my $sizestr = extract_param
($param, 'size');
4361 my $skiplock = extract_param
($param, 'skiplock');
4362 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4363 if $skiplock && $authuser ne 'root@pam';
4365 my $storecfg = PVE
::Storage
::config
();
4367 my $updatefn = sub {
4369 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4371 die "checksum missmatch (file change by other user?)\n"
4372 if $digest && $digest ne $conf->{digest
};
4373 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4375 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4377 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4379 my (undef, undef, undef, undef, undef, undef, $format) =
4380 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4382 die "can't resize volume: $disk if snapshot exists\n"
4383 if %{$conf->{snapshots
}} && $format eq 'qcow2';
4385 my $volid = $drive->{file
};
4387 die "disk '$disk' has no associated volume\n" if !$volid;
4389 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4391 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4393 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4395 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4396 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4398 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4400 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4401 my ($ext, $newsize, $unit) = ($1, $2, $4);
4404 $newsize = $newsize * 1024;
4405 } elsif ($unit eq 'M') {
4406 $newsize = $newsize * 1024 * 1024;
4407 } elsif ($unit eq 'G') {
4408 $newsize = $newsize * 1024 * 1024 * 1024;
4409 } elsif ($unit eq 'T') {
4410 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4413 $newsize += $size if $ext;
4414 $newsize = int($newsize);
4416 die "shrinking disks is not supported\n" if $newsize < $size;
4418 return if $size == $newsize;
4420 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4422 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4424 $drive->{size
} = $newsize;
4425 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4427 PVE
::QemuConfig-
>write_config($vmid, $conf);
4430 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4434 __PACKAGE__-
>register_method({
4435 name
=> 'snapshot_list',
4436 path
=> '{vmid}/snapshot',
4438 description
=> "List all snapshots.",
4440 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4443 protected
=> 1, # qemu pid files are only readable by root
4445 additionalProperties
=> 0,
4447 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4448 node
=> get_standard_option
('pve-node'),
4457 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4461 description
=> "Snapshot includes RAM.",
4466 description
=> "Snapshot description.",
4470 description
=> "Snapshot creation time",
4472 renderer
=> 'timestamp',
4476 description
=> "Parent snapshot identifier.",
4482 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4487 my $vmid = $param->{vmid
};
4489 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4490 my $snaphash = $conf->{snapshots
} || {};
4494 foreach my $name (keys %$snaphash) {
4495 my $d = $snaphash->{$name};
4498 snaptime
=> $d->{snaptime
} || 0,
4499 vmstate
=> $d->{vmstate
} ?
1 : 0,
4500 description
=> $d->{description
} || '',
4502 $item->{parent
} = $d->{parent
} if $d->{parent
};
4503 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4507 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4510 digest
=> $conf->{digest
},
4511 running
=> $running,
4512 description
=> "You are here!",
4514 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4516 push @$res, $current;
4521 __PACKAGE__-
>register_method({
4523 path
=> '{vmid}/snapshot',
4527 description
=> "Snapshot a VM.",
4529 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4532 additionalProperties
=> 0,
4534 node
=> get_standard_option
('pve-node'),
4535 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4536 snapname
=> get_standard_option
('pve-snapshot-name'),
4540 description
=> "Save the vmstate",
4545 description
=> "A textual description or comment.",
4551 description
=> "the task ID.",
4556 my $rpcenv = PVE
::RPCEnvironment
::get
();
4558 my $authuser = $rpcenv->get_user();
4560 my $node = extract_param
($param, 'node');
4562 my $vmid = extract_param
($param, 'vmid');
4564 my $snapname = extract_param
($param, 'snapname');
4566 die "unable to use snapshot name 'current' (reserved name)\n"
4567 if $snapname eq 'current';
4569 die "unable to use snapshot name 'pending' (reserved name)\n"
4570 if lc($snapname) eq 'pending';
4573 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4574 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4575 $param->{description
});
4578 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4581 __PACKAGE__-
>register_method({
4582 name
=> 'snapshot_cmd_idx',
4583 path
=> '{vmid}/snapshot/{snapname}',
4590 additionalProperties
=> 0,
4592 vmid
=> get_standard_option
('pve-vmid'),
4593 node
=> get_standard_option
('pve-node'),
4594 snapname
=> get_standard_option
('pve-snapshot-name'),
4603 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4610 push @$res, { cmd
=> 'rollback' };
4611 push @$res, { cmd
=> 'config' };
4616 __PACKAGE__-
>register_method({
4617 name
=> 'update_snapshot_config',
4618 path
=> '{vmid}/snapshot/{snapname}/config',
4622 description
=> "Update snapshot metadata.",
4624 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4627 additionalProperties
=> 0,
4629 node
=> get_standard_option
('pve-node'),
4630 vmid
=> get_standard_option
('pve-vmid'),
4631 snapname
=> get_standard_option
('pve-snapshot-name'),
4635 description
=> "A textual description or comment.",
4639 returns
=> { type
=> 'null' },
4643 my $rpcenv = PVE
::RPCEnvironment
::get
();
4645 my $authuser = $rpcenv->get_user();
4647 my $vmid = extract_param
($param, 'vmid');
4649 my $snapname = extract_param
($param, 'snapname');
4651 return if !defined($param->{description
});
4653 my $updatefn = sub {
4655 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4657 PVE
::QemuConfig-
>check_lock($conf);
4659 my $snap = $conf->{snapshots
}->{$snapname};
4661 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4663 $snap->{description
} = $param->{description
} if defined($param->{description
});
4665 PVE
::QemuConfig-
>write_config($vmid, $conf);
4668 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4673 __PACKAGE__-
>register_method({
4674 name
=> 'get_snapshot_config',
4675 path
=> '{vmid}/snapshot/{snapname}/config',
4678 description
=> "Get snapshot configuration",
4680 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4683 additionalProperties
=> 0,
4685 node
=> get_standard_option
('pve-node'),
4686 vmid
=> get_standard_option
('pve-vmid'),
4687 snapname
=> get_standard_option
('pve-snapshot-name'),
4690 returns
=> { type
=> "object" },
4694 my $rpcenv = PVE
::RPCEnvironment
::get
();
4696 my $authuser = $rpcenv->get_user();
4698 my $vmid = extract_param
($param, 'vmid');
4700 my $snapname = extract_param
($param, 'snapname');
4702 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4704 my $snap = $conf->{snapshots
}->{$snapname};
4706 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4711 __PACKAGE__-
>register_method({
4713 path
=> '{vmid}/snapshot/{snapname}/rollback',
4717 description
=> "Rollback VM state to specified snapshot.",
4719 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4722 additionalProperties
=> 0,
4724 node
=> get_standard_option
('pve-node'),
4725 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4726 snapname
=> get_standard_option
('pve-snapshot-name'),
4731 description
=> "the task ID.",
4736 my $rpcenv = PVE
::RPCEnvironment
::get
();
4738 my $authuser = $rpcenv->get_user();
4740 my $node = extract_param
($param, 'node');
4742 my $vmid = extract_param
($param, 'vmid');
4744 my $snapname = extract_param
($param, 'snapname');
4747 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4748 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4752 # hold migration lock, this makes sure that nobody create replication snapshots
4753 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4756 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4759 __PACKAGE__-
>register_method({
4760 name
=> 'delsnapshot',
4761 path
=> '{vmid}/snapshot/{snapname}',
4765 description
=> "Delete a VM snapshot.",
4767 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4770 additionalProperties
=> 0,
4772 node
=> get_standard_option
('pve-node'),
4773 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4774 snapname
=> get_standard_option
('pve-snapshot-name'),
4778 description
=> "For removal from config file, even if removing disk snapshots fails.",
4784 description
=> "the task ID.",
4789 my $rpcenv = PVE
::RPCEnvironment
::get
();
4791 my $authuser = $rpcenv->get_user();
4793 my $node = extract_param
($param, 'node');
4795 my $vmid = extract_param
($param, 'vmid');
4797 my $snapname = extract_param
($param, 'snapname');
4800 my $do_delete = sub {
4802 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4803 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4807 if ($param->{force
}) {
4810 eval { PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $do_delete); };
4812 die $err if $lock_obtained;
4813 die "Failed to obtain guest migration lock - replication running?\n";
4818 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4821 __PACKAGE__-
>register_method({
4823 path
=> '{vmid}/template',
4827 description
=> "Create a Template.",
4829 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4830 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4833 additionalProperties
=> 0,
4835 node
=> get_standard_option
('pve-node'),
4836 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4840 description
=> "If you want to convert only 1 disk to base image.",
4841 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4848 description
=> "the task ID.",
4853 my $rpcenv = PVE
::RPCEnvironment
::get
();
4855 my $authuser = $rpcenv->get_user();
4857 my $node = extract_param
($param, 'node');
4859 my $vmid = extract_param
($param, 'vmid');
4861 my $disk = extract_param
($param, 'disk');
4863 my $load_and_check = sub {
4864 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4866 PVE
::QemuConfig-
>check_lock($conf);
4868 die "unable to create template, because VM contains snapshots\n"
4869 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4871 die "you can't convert a template to a template\n"
4872 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4874 die "you can't convert a VM to template if VM is running\n"
4875 if PVE
::QemuServer
::check_running
($vmid);
4880 $load_and_check->();
4883 PVE
::QemuConfig-
>lock_config($vmid, sub {
4884 my $conf = $load_and_check->();
4886 $conf->{template
} = 1;
4887 PVE
::QemuConfig-
>write_config($vmid, $conf);
4889 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4893 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4896 __PACKAGE__-
>register_method({
4897 name
=> 'cloudinit_generated_config_dump',
4898 path
=> '{vmid}/cloudinit/dump',
4901 description
=> "Get automatically generated cloudinit config.",
4903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4906 additionalProperties
=> 0,
4908 node
=> get_standard_option
('pve-node'),
4909 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4911 description
=> 'Config type.',
4913 enum
=> ['user', 'network', 'meta'],
4923 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4925 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});