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
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
27 use PVE
::QemuServer
::Machine
;
29 use PVE
::RPCEnvironment
;
30 use PVE
::AccessControl
;
34 use PVE
::API2
::Firewall
::VM
;
35 use PVE
::API2
::Qemu
::Agent
;
36 use PVE
::VZDump
::Plugin
;
37 use PVE
::DataCenterConfig
;
42 if (!$ENV{PVE_GENERATING_DOCS
}) {
43 require PVE
::HA
::Env
::PVE2
;
44 import PVE
::HA
::Env
::PVE2
;
45 require PVE
::HA
::Config
;
46 import PVE
::HA
::Config
;
50 use Data
::Dumper
; # fixme: remove
52 use base
qw(PVE::RESTHandler);
54 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.";
56 my $resolve_cdrom_alias = sub {
59 if (my $value = $param->{cdrom
}) {
60 $value .= ",media=cdrom" if $value !~ m/media=/;
61 $param->{ide2
} = $value;
62 delete $param->{cdrom
};
66 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
67 my $check_storage_access = sub {
68 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
70 PVE
::QemuConfig-
>foreach_volume($settings, sub {
71 my ($ds, $drive) = @_;
73 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
75 my $volid = $drive->{file
};
76 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
78 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
80 } elsif ($isCDROM && ($volid eq 'cdrom')) {
81 $rpcenv->check($authuser, "/", ['Sys.Console']);
82 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
83 my ($storeid, $size) = ($2 || $default_storage, $3);
84 die "no storage ID specified (and no default storage)\n" if !$storeid;
85 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
86 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
87 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
88 if !$scfg->{content
}->{images
};
90 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
94 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
95 if defined($settings->{vmstatestorage
});
98 my $check_storage_access_clone = sub {
99 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
103 PVE
::QemuConfig-
>foreach_volume($conf, sub {
104 my ($ds, $drive) = @_;
106 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
108 my $volid = $drive->{file
};
110 return if !$volid || $volid eq 'none';
113 if ($volid eq 'cdrom') {
114 $rpcenv->check($authuser, "/", ['Sys.Console']);
116 # we simply allow access
117 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
118 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
119 $sharedvm = 0 if !$scfg->{shared
};
123 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
124 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
125 $sharedvm = 0 if !$scfg->{shared
};
127 $sid = $storage if $storage;
128 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
132 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
133 if defined($conf->{vmstatestorage
});
138 # Note: $pool is only needed when creating a VM, because pool permissions
139 # are automatically inherited if VM already exists inside a pool.
140 my $create_disks = sub {
141 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
148 my ($ds, $disk) = @_;
150 my $volid = $disk->{file
};
151 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
153 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
154 delete $disk->{size
};
155 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
156 } elsif (defined($volname) && $volname eq 'cloudinit') {
157 $storeid = $storeid // $default_storage;
158 die "no storage ID specified (and no default storage)\n" if !$storeid;
159 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
160 my $name = "vm-$vmid-cloudinit";
164 $fmt = $disk->{format
} // "qcow2";
167 $fmt = $disk->{format
} // "raw";
170 # Initial disk created with 4 MB and aligned to 4MB on regeneration
171 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
172 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
173 $disk->{file
} = $volid;
174 $disk->{media
} = 'cdrom';
175 push @$vollist, $volid;
176 delete $disk->{format
}; # no longer needed
177 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
178 } elsif ($volid =~ $NEW_DISK_RE) {
179 my ($storeid, $size) = ($2 || $default_storage, $3);
180 die "no storage ID specified (and no default storage)\n" if !$storeid;
181 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
182 my $fmt = $disk->{format
} || $defformat;
184 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
187 if ($ds eq 'efidisk0') {
188 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
189 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
190 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
191 } elsif ($ds eq 'tpmstate0') {
192 # swtpm can only use raw volumes, and uses a fixed size
193 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
194 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
196 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
198 push @$vollist, $volid;
199 $disk->{file
} = $volid;
200 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
201 delete $disk->{format
}; # no longer needed
202 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
205 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
207 my $volid_is_new = 1;
210 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
211 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
216 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
218 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
220 die "volume $volid does not exist\n" if !$size;
222 $disk->{size
} = $size;
225 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
229 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
231 # free allocated images on error
233 syslog
('err', "VM $vmid creating disks failed");
234 foreach my $volid (@$vollist) {
235 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
241 # modify vm config if everything went well
242 foreach my $ds (keys %$res) {
243 $conf->{$ds} = $res->{$ds};
249 my $check_cpu_model_access = sub {
250 my ($rpcenv, $authuser, $new, $existing) = @_;
252 return if !defined($new->{cpu
});
254 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
255 return if !$cpu || !$cpu->{cputype
}; # always allow default
256 my $cputype = $cpu->{cputype
};
258 if ($existing && $existing->{cpu
}) {
259 # changing only other settings doesn't require permissions for CPU model
260 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
261 return if $existingCpu->{cputype
} eq $cputype;
264 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
265 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
280 my $memoryoptions = {
286 my $hwtypeoptions = {
299 my $generaloptions = {
306 'migrate_downtime' => 1,
307 'migrate_speed' => 1,
320 my $vmpoweroptions = {
327 'vmstatestorage' => 1,
330 my $cloudinitoptions = {
340 my $check_vm_create_serial_perm = sub {
341 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
343 return 1 if $authuser eq 'root@pam';
345 foreach my $opt (keys %{$param}) {
346 next if $opt !~ m/^serial\d+$/;
348 if ($param->{$opt} eq 'socket') {
349 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
351 die "only root can set '$opt' config for real devices\n";
358 my $check_vm_create_usb_perm = sub {
359 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
361 return 1 if $authuser eq 'root@pam';
363 foreach my $opt (keys %{$param}) {
364 next if $opt !~ m/^usb\d+$/;
366 if ($param->{$opt} =~ m/spice/) {
367 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
369 die "only root can set '$opt' config for real devices\n";
376 my $check_vm_modify_config_perm = sub {
377 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
379 return 1 if $authuser eq 'root@pam';
381 foreach my $opt (@$key_list) {
382 # some checks (e.g., disk, serial port, usb) need to be done somewhere
383 # else, as there the permission can be value dependend
384 next if PVE
::QemuServer
::is_valid_drivename
($opt);
385 next if $opt eq 'cdrom';
386 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
389 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
390 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
391 } elsif ($memoryoptions->{$opt}) {
392 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
393 } elsif ($hwtypeoptions->{$opt}) {
394 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
395 } elsif ($generaloptions->{$opt}) {
396 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
397 # special case for startup since it changes host behaviour
398 if ($opt eq 'startup') {
399 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
401 } elsif ($vmpoweroptions->{$opt}) {
402 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
403 } elsif ($diskoptions->{$opt}) {
404 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
405 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
406 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
407 } elsif ($cloudinitoptions->{$opt}) {
408 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
409 } elsif ($opt eq 'vmstate') {
410 # the user needs Disk and PowerMgmt privileges to change the vmstate
411 # also needs privileges on the storage, that will be checked later
412 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
414 # catches hostpci\d+, args, lock, etc.
415 # new options will be checked here
416 die "only root can set '$opt' config\n";
423 __PACKAGE__-
>register_method({
427 description
=> "Virtual machine index (per node).",
429 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
433 protected
=> 1, # qemu pid files are only readable by root
435 additionalProperties
=> 0,
437 node
=> get_standard_option
('pve-node'),
441 description
=> "Determine the full status of active VMs.",
449 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
451 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
456 my $rpcenv = PVE
::RPCEnvironment
::get
();
457 my $authuser = $rpcenv->get_user();
459 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
462 foreach my $vmid (keys %$vmstatus) {
463 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
465 my $data = $vmstatus->{$vmid};
472 my $parse_restore_archive = sub {
473 my ($storecfg, $archive) = @_;
475 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
477 if (defined($archive_storeid)) {
478 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
479 if ($scfg->{type
} eq 'pbs') {
486 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
494 __PACKAGE__-
>register_method({
498 description
=> "Create or restore a virtual machine.",
500 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
501 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
502 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
503 user
=> 'all', # check inside
508 additionalProperties
=> 0,
509 properties
=> PVE
::QemuServer
::json_config_properties
(
511 node
=> get_standard_option
('pve-node'),
512 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
514 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.",
518 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
520 storage
=> get_standard_option
('pve-storage-id', {
521 description
=> "Default storage.",
523 completion
=> \
&PVE
::QemuServer
::complete_storage
,
528 description
=> "Allow to overwrite existing VM.",
529 requires
=> 'archive',
534 description
=> "Assign a unique random ethernet address.",
535 requires
=> 'archive',
540 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
541 requires
=> 'archive',
545 type
=> 'string', format
=> 'pve-poolid',
546 description
=> "Add the VM to the specified pool.",
549 description
=> "Override I/O bandwidth limit (in KiB/s).",
553 default => 'restore limit from datacenter or storage config',
559 description
=> "Start VM after it was created successfully.",
569 my $rpcenv = PVE
::RPCEnvironment
::get
();
570 my $authuser = $rpcenv->get_user();
572 my $node = extract_param
($param, 'node');
573 my $vmid = extract_param
($param, 'vmid');
575 my $archive = extract_param
($param, 'archive');
576 my $is_restore = !!$archive;
578 my $bwlimit = extract_param
($param, 'bwlimit');
579 my $force = extract_param
($param, 'force');
580 my $pool = extract_param
($param, 'pool');
581 my $start_after_create = extract_param
($param, 'start');
582 my $storage = extract_param
($param, 'storage');
583 my $unique = extract_param
($param, 'unique');
584 my $live_restore = extract_param
($param, 'live-restore');
586 if (defined(my $ssh_keys = $param->{sshkeys
})) {
587 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
588 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
591 PVE
::Cluster
::check_cfs_quorum
();
593 my $filename = PVE
::QemuConfig-
>config_file($vmid);
594 my $storecfg = PVE
::Storage
::config
();
596 if (defined($pool)) {
597 $rpcenv->check_pool_exist($pool);
600 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
601 if defined($storage);
603 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
605 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
607 } elsif ($archive && $force && (-f
$filename) &&
608 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
609 # OK: user has VM.Backup permissions, and want to restore an existing VM
615 &$resolve_cdrom_alias($param);
617 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
619 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
621 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
622 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
624 &$check_cpu_model_access($rpcenv, $authuser, $param);
626 foreach my $opt (keys %$param) {
627 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
628 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
629 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
631 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
632 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
636 PVE
::QemuServer
::add_random_macs
($param);
638 my $keystr = join(' ', keys %$param);
639 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
641 if ($archive eq '-') {
642 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
643 $archive = { type
=> 'pipe' };
645 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
647 $archive = $parse_restore_archive->($storecfg, $archive);
651 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
653 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
654 die "$emsg $@" if $@;
656 my $restored_data = 0;
657 my $restorefn = sub {
658 my $conf = PVE
::QemuConfig-
>load_config($vmid);
660 PVE
::QemuConfig-
>check_protection($conf, $emsg);
662 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
665 my $restore_options = {
670 live
=> $live_restore,
672 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
673 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
675 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
676 } elsif ($archive->{type
} eq 'pbs') {
677 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
679 die "unknown backup archive type\n";
683 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
684 # Convert restored VM to template if backup was VM template
685 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
686 warn "Convert to template.\n";
687 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
692 # ensure no old replication state are exists
693 PVE
::ReplicationState
::delete_guest_states
($vmid);
695 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
697 if ($start_after_create && !$live_restore) {
698 print "Execute autostart\n";
699 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
705 # ensure no old replication state are exists
706 PVE
::ReplicationState
::delete_guest_states
($vmid);
710 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
712 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
716 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
718 if (!$conf->{boot
}) {
719 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
720 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
723 # auto generate uuid if user did not specify smbios1 option
724 if (!$conf->{smbios1
}) {
725 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
728 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
729 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
732 my $machine = $conf->{machine
};
733 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
734 # always pin Windows' machine version on create, they get to easily confused
735 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
736 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
740 PVE
::QemuConfig-
>write_config($vmid, $conf);
746 foreach my $volid (@$vollist) {
747 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
753 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
756 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
758 if ($start_after_create) {
759 print "Execute autostart\n";
760 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
765 my ($code, $worker_name);
767 $worker_name = 'qmrestore';
769 eval { $restorefn->() };
771 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
773 if ($restored_data) {
774 warn "error after data was restored, VM disks should be OK but config may "
775 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
777 warn "error before or during data restore, some or all disks were not "
778 ."completely restored. VM $vmid state is NOT cleaned up.\n";
784 $worker_name = 'qmcreate';
786 eval { $createfn->() };
789 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
790 unlink($conffile) or die "failed to remove config file: $!\n";
798 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
801 __PACKAGE__-
>register_method({
806 description
=> "Directory index",
811 additionalProperties
=> 0,
813 node
=> get_standard_option
('pve-node'),
814 vmid
=> get_standard_option
('pve-vmid'),
822 subdir
=> { type
=> 'string' },
825 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
831 { subdir
=> 'config' },
832 { subdir
=> 'pending' },
833 { subdir
=> 'status' },
834 { subdir
=> 'unlink' },
835 { subdir
=> 'vncproxy' },
836 { subdir
=> 'termproxy' },
837 { subdir
=> 'migrate' },
838 { subdir
=> 'resize' },
839 { subdir
=> 'move' },
841 { subdir
=> 'rrddata' },
842 { subdir
=> 'monitor' },
843 { subdir
=> 'agent' },
844 { subdir
=> 'snapshot' },
845 { subdir
=> 'spiceproxy' },
846 { subdir
=> 'sendkey' },
847 { subdir
=> 'firewall' },
853 __PACKAGE__-
>register_method ({
854 subclass
=> "PVE::API2::Firewall::VM",
855 path
=> '{vmid}/firewall',
858 __PACKAGE__-
>register_method ({
859 subclass
=> "PVE::API2::Qemu::Agent",
860 path
=> '{vmid}/agent',
863 __PACKAGE__-
>register_method({
865 path
=> '{vmid}/rrd',
867 protected
=> 1, # fixme: can we avoid that?
869 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
871 description
=> "Read VM RRD statistics (returns PNG)",
873 additionalProperties
=> 0,
875 node
=> get_standard_option
('pve-node'),
876 vmid
=> get_standard_option
('pve-vmid'),
878 description
=> "Specify the time frame you are interested in.",
880 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
883 description
=> "The list of datasources you want to display.",
884 type
=> 'string', format
=> 'pve-configid-list',
887 description
=> "The RRD consolidation function",
889 enum
=> [ 'AVERAGE', 'MAX' ],
897 filename
=> { type
=> 'string' },
903 return PVE
::RRD
::create_rrd_graph
(
904 "pve2-vm/$param->{vmid}", $param->{timeframe
},
905 $param->{ds
}, $param->{cf
});
909 __PACKAGE__-
>register_method({
911 path
=> '{vmid}/rrddata',
913 protected
=> 1, # fixme: can we avoid that?
915 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
917 description
=> "Read VM RRD statistics",
919 additionalProperties
=> 0,
921 node
=> get_standard_option
('pve-node'),
922 vmid
=> get_standard_option
('pve-vmid'),
924 description
=> "Specify the time frame you are interested in.",
926 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
929 description
=> "The RRD consolidation function",
931 enum
=> [ 'AVERAGE', 'MAX' ],
946 return PVE
::RRD
::create_rrd_data
(
947 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
951 __PACKAGE__-
>register_method({
953 path
=> '{vmid}/config',
956 description
=> "Get the virtual machine configuration with pending configuration " .
957 "changes applied. Set the 'current' parameter to get the current configuration instead.",
959 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
962 additionalProperties
=> 0,
964 node
=> get_standard_option
('pve-node'),
965 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
967 description
=> "Get current values (instead of pending values).",
972 snapshot
=> get_standard_option
('pve-snapshot-name', {
973 description
=> "Fetch config values from given snapshot.",
976 my ($cmd, $pname, $cur, $args) = @_;
977 PVE
::QemuConfig-
>snapshot_list($args->[0]);
983 description
=> "The VM configuration.",
985 properties
=> PVE
::QemuServer
::json_config_properties
({
988 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
995 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
996 current
=> "cannot use 'snapshot' parameter with 'current'"})
997 if ($param->{snapshot
} && $param->{current
});
1000 if ($param->{snapshot
}) {
1001 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1003 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1005 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1010 __PACKAGE__-
>register_method({
1011 name
=> 'vm_pending',
1012 path
=> '{vmid}/pending',
1015 description
=> "Get the virtual machine configuration with both current and pending values.",
1017 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1020 additionalProperties
=> 0,
1022 node
=> get_standard_option
('pve-node'),
1023 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1032 description
=> "Configuration option name.",
1036 description
=> "Current value.",
1041 description
=> "Pending value.",
1046 description
=> "Indicates a pending delete request if present and not 0. " .
1047 "The value 2 indicates a force-delete request.",
1059 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1061 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1063 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1064 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1066 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1069 # POST/PUT {vmid}/config implementation
1071 # The original API used PUT (idempotent) an we assumed that all operations
1072 # are fast. But it turned out that almost any configuration change can
1073 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1074 # time to complete and have side effects (not idempotent).
1076 # The new implementation uses POST and forks a worker process. We added
1077 # a new option 'background_delay'. If specified we wait up to
1078 # 'background_delay' second for the worker task to complete. It returns null
1079 # if the task is finished within that time, else we return the UPID.
1081 my $update_vm_api = sub {
1082 my ($param, $sync) = @_;
1084 my $rpcenv = PVE
::RPCEnvironment
::get
();
1086 my $authuser = $rpcenv->get_user();
1088 my $node = extract_param
($param, 'node');
1090 my $vmid = extract_param
($param, 'vmid');
1092 my $digest = extract_param
($param, 'digest');
1094 my $background_delay = extract_param
($param, 'background_delay');
1096 if (defined(my $cipassword = $param->{cipassword
})) {
1097 # Same logic as in cloud-init (but with the regex fixed...)
1098 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1099 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1102 my @paramarr = (); # used for log message
1103 foreach my $key (sort keys %$param) {
1104 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1105 push @paramarr, "-$key", $value;
1108 my $skiplock = extract_param
($param, 'skiplock');
1109 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1110 if $skiplock && $authuser ne 'root@pam';
1112 my $delete_str = extract_param
($param, 'delete');
1114 my $revert_str = extract_param
($param, 'revert');
1116 my $force = extract_param
($param, 'force');
1118 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1119 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1120 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1123 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1125 my $storecfg = PVE
::Storage
::config
();
1127 my $defaults = PVE
::QemuServer
::load_defaults
();
1129 &$resolve_cdrom_alias($param);
1131 # now try to verify all parameters
1134 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1135 if (!PVE
::QemuServer
::option_exists
($opt)) {
1136 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1139 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1140 "-revert $opt' at the same time" })
1141 if defined($param->{$opt});
1143 $revert->{$opt} = 1;
1147 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1148 $opt = 'ide2' if $opt eq 'cdrom';
1150 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1151 "-delete $opt' at the same time" })
1152 if defined($param->{$opt});
1154 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1155 "-revert $opt' at the same time" })
1158 if (!PVE
::QemuServer
::option_exists
($opt)) {
1159 raise_param_exc
({ delete => "unknown option '$opt'" });
1165 my $repl_conf = PVE
::ReplicationConfig-
>new();
1166 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1167 my $check_replication = sub {
1169 return if !$is_replicated;
1170 my $volid = $drive->{file
};
1171 return if !$volid || !($drive->{replicate
}//1);
1172 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1174 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1175 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1176 if !defined($storeid);
1178 return if defined($volname) && $volname eq 'cloudinit';
1181 if ($volid =~ $NEW_DISK_RE) {
1183 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1185 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1187 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1188 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1189 return if $scfg->{shared
};
1190 die "cannot add non-replicatable volume to a replicated VM\n";
1193 foreach my $opt (keys %$param) {
1194 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1195 # cleanup drive path
1196 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1197 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1198 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1199 $check_replication->($drive);
1200 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1201 } elsif ($opt =~ m/^net(\d+)$/) {
1203 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1204 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1205 } elsif ($opt eq 'vmgenid') {
1206 if ($param->{$opt} eq '1') {
1207 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1209 } elsif ($opt eq 'hookscript') {
1210 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1211 raise_param_exc
({ $opt => $@ }) if $@;
1215 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1217 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1219 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1221 my $updatefn = sub {
1223 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1225 die "checksum missmatch (file change by other user?)\n"
1226 if $digest && $digest ne $conf->{digest
};
1228 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1230 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1231 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1232 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1233 delete $conf->{lock}; # for check lock check, not written out
1234 push @delete, 'lock'; # this is the real deal to write it out
1236 push @delete, 'runningmachine' if $conf->{runningmachine
};
1237 push @delete, 'runningcpu' if $conf->{runningcpu
};
1240 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1242 foreach my $opt (keys %$revert) {
1243 if (defined($conf->{$opt})) {
1244 $param->{$opt} = $conf->{$opt};
1245 } elsif (defined($conf->{pending
}->{$opt})) {
1250 if ($param->{memory
} || defined($param->{balloon
})) {
1251 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1252 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1254 die "balloon value too large (must be smaller than assigned memory)\n"
1255 if $balloon && $balloon > $maxmem;
1258 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1262 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1264 # write updates to pending section
1266 my $modified = {}; # record what $option we modify
1269 if (my $boot = $conf->{boot
}) {
1270 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1271 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1273 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1275 my $check_drive_perms = sub {
1276 my ($opt, $val) = @_;
1277 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1278 # FIXME: cloudinit: CDROM or Disk?
1279 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1280 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1282 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1286 foreach my $opt (@delete) {
1287 $modified->{$opt} = 1;
1288 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1290 # value of what we want to delete, independent if pending or not
1291 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1292 if (!defined($val)) {
1293 warn "cannot delete '$opt' - not set in current configuration!\n";
1294 $modified->{$opt} = 0;
1297 my $is_pending_val = defined($conf->{pending
}->{$opt});
1298 delete $conf->{pending
}->{$opt};
1300 # remove from bootorder if necessary
1301 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1302 @bootorder = grep {$_ ne $opt} @bootorder;
1303 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1304 $modified->{boot
} = 1;
1307 if ($opt =~ m/^unused/) {
1308 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1309 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1310 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1311 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1312 delete $conf->{$opt};
1313 PVE
::QemuConfig-
>write_config($vmid, $conf);
1315 } elsif ($opt eq 'vmstate') {
1316 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1317 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1318 delete $conf->{$opt};
1319 PVE
::QemuConfig-
>write_config($vmid, $conf);
1321 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1322 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1323 $check_drive_perms->($opt, $val);
1324 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1326 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1327 PVE
::QemuConfig-
>write_config($vmid, $conf);
1328 } elsif ($opt =~ m/^serial\d+$/) {
1329 if ($val eq 'socket') {
1330 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1331 } elsif ($authuser ne 'root@pam') {
1332 die "only root can delete '$opt' config for real devices\n";
1334 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1335 PVE
::QemuConfig-
>write_config($vmid, $conf);
1336 } elsif ($opt =~ m/^usb\d+$/) {
1337 if ($val =~ m/spice/) {
1338 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1339 } elsif ($authuser ne 'root@pam') {
1340 die "only root can delete '$opt' config for real devices\n";
1342 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1343 PVE
::QemuConfig-
>write_config($vmid, $conf);
1345 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1346 PVE
::QemuConfig-
>write_config($vmid, $conf);
1350 foreach my $opt (keys %$param) { # add/change
1351 $modified->{$opt} = 1;
1352 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1353 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1355 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1357 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1359 if ($conf->{$opt}) {
1360 $check_drive_perms->($opt, $conf->{$opt});
1364 $check_drive_perms->($opt, $param->{$opt});
1365 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1366 if defined($conf->{pending
}->{$opt});
1368 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1370 # default legacy boot order implies all cdroms anyway
1372 # append new CD drives to bootorder to mark them bootable
1373 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1374 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1375 push @bootorder, $opt;
1376 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1377 $modified->{boot
} = 1;
1380 } elsif ($opt =~ m/^serial\d+/) {
1381 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1382 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1383 } elsif ($authuser ne 'root@pam') {
1384 die "only root can modify '$opt' config for real devices\n";
1386 $conf->{pending
}->{$opt} = $param->{$opt};
1387 } elsif ($opt =~ m/^usb\d+/) {
1388 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1389 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1390 } elsif ($authuser ne 'root@pam') {
1391 die "only root can modify '$opt' config for real devices\n";
1393 $conf->{pending
}->{$opt} = $param->{$opt};
1395 $conf->{pending
}->{$opt} = $param->{$opt};
1397 if ($opt eq 'boot') {
1398 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1399 if ($new_bootcfg->{order
}) {
1400 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1401 for my $dev (@devs) {
1402 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1403 my $deleted = grep {$_ eq $dev} @delete;
1404 die "invalid bootorder: device '$dev' does not exist'\n"
1405 if !$exists || $deleted;
1408 # remove legacy boot order settings if new one set
1409 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1410 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1411 if $conf->{bootdisk
};
1415 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1416 PVE
::QemuConfig-
>write_config($vmid, $conf);
1419 # remove pending changes when nothing changed
1420 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1421 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1422 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1424 return if !scalar(keys %{$conf->{pending
}});
1426 my $running = PVE
::QemuServer
::check_running
($vmid);
1428 # apply pending changes
1430 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1434 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1436 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1438 raise_param_exc
($errors) if scalar(keys %$errors);
1447 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1449 if ($background_delay) {
1451 # Note: It would be better to do that in the Event based HTTPServer
1452 # to avoid blocking call to sleep.
1454 my $end_time = time() + $background_delay;
1456 my $task = PVE
::Tools
::upid_decode
($upid);
1459 while (time() < $end_time) {
1460 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1462 sleep(1); # this gets interrupted when child process ends
1466 my $status = PVE
::Tools
::upid_read_status
($upid);
1467 return if !PVE
::Tools
::upid_status_is_error
($status);
1476 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1479 my $vm_config_perm_list = [
1484 'VM.Config.Network',
1486 'VM.Config.Options',
1487 'VM.Config.Cloudinit',
1490 __PACKAGE__-
>register_method({
1491 name
=> 'update_vm_async',
1492 path
=> '{vmid}/config',
1496 description
=> "Set virtual machine options (asynchrounous API).",
1498 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1501 additionalProperties
=> 0,
1502 properties
=> PVE
::QemuServer
::json_config_properties
(
1504 node
=> get_standard_option
('pve-node'),
1505 vmid
=> get_standard_option
('pve-vmid'),
1506 skiplock
=> get_standard_option
('skiplock'),
1508 type
=> 'string', format
=> 'pve-configid-list',
1509 description
=> "A list of settings you want to delete.",
1513 type
=> 'string', format
=> 'pve-configid-list',
1514 description
=> "Revert a pending change.",
1519 description
=> $opt_force_description,
1521 requires
=> 'delete',
1525 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1529 background_delay
=> {
1531 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1542 code
=> $update_vm_api,
1545 __PACKAGE__-
>register_method({
1546 name
=> 'update_vm',
1547 path
=> '{vmid}/config',
1551 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1553 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1556 additionalProperties
=> 0,
1557 properties
=> PVE
::QemuServer
::json_config_properties
(
1559 node
=> get_standard_option
('pve-node'),
1560 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1561 skiplock
=> get_standard_option
('skiplock'),
1563 type
=> 'string', format
=> 'pve-configid-list',
1564 description
=> "A list of settings you want to delete.",
1568 type
=> 'string', format
=> 'pve-configid-list',
1569 description
=> "Revert a pending change.",
1574 description
=> $opt_force_description,
1576 requires
=> 'delete',
1580 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1586 returns
=> { type
=> 'null' },
1589 &$update_vm_api($param, 1);
1594 __PACKAGE__-
>register_method({
1595 name
=> 'destroy_vm',
1600 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1601 ." and firewall rules",
1603 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1606 additionalProperties
=> 0,
1608 node
=> get_standard_option
('pve-node'),
1609 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1610 skiplock
=> get_standard_option
('skiplock'),
1613 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1616 'destroy-unreferenced-disks' => {
1618 description
=> "If set, destroy additionally all disks not referenced in the config"
1619 ." but with a matching VMID from all enabled storages.",
1631 my $rpcenv = PVE
::RPCEnvironment
::get
();
1632 my $authuser = $rpcenv->get_user();
1633 my $vmid = $param->{vmid
};
1635 my $skiplock = $param->{skiplock
};
1636 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1637 if $skiplock && $authuser ne 'root@pam';
1639 my $early_checks = sub {
1641 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1642 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1644 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1646 if (!$param->{purge
}) {
1647 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1649 # don't allow destroy if with replication jobs but no purge param
1650 my $repl_conf = PVE
::ReplicationConfig-
>new();
1651 $repl_conf->check_for_existing_jobs($vmid);
1654 die "VM $vmid is running - destroy failed\n"
1655 if PVE
::QemuServer
::check_running
($vmid);
1665 my $storecfg = PVE
::Storage
::config
();
1667 syslog
('info', "destroy VM $vmid: $upid\n");
1668 PVE
::QemuConfig-
>lock_config($vmid, sub {
1669 # repeat, config might have changed
1670 my $ha_managed = $early_checks->();
1672 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1674 PVE
::QemuServer
::destroy_vm
(
1677 $skiplock, { lock => 'destroyed' },
1678 $purge_unreferenced,
1681 PVE
::AccessControl
::remove_vm_access
($vmid);
1682 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1683 if ($param->{purge
}) {
1684 print "purging VM $vmid from related configurations..\n";
1685 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1686 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1689 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1690 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1694 # only now remove the zombie config, else we can have reuse race
1695 PVE
::QemuConfig-
>destroy_config($vmid);
1699 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1702 __PACKAGE__-
>register_method({
1704 path
=> '{vmid}/unlink',
1708 description
=> "Unlink/delete disk images.",
1710 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1713 additionalProperties
=> 0,
1715 node
=> get_standard_option
('pve-node'),
1716 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1718 type
=> 'string', format
=> 'pve-configid-list',
1719 description
=> "A list of disk IDs you want to delete.",
1723 description
=> $opt_force_description,
1728 returns
=> { type
=> 'null'},
1732 $param->{delete} = extract_param
($param, 'idlist');
1734 __PACKAGE__-
>update_vm($param);
1739 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1740 my $gen_rand_chars = sub {
1743 die "invalid length $length" if $length < 1;
1745 my $min = ord('!'); # first printable ascii
1747 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1748 die "failed to generate random bytes!\n"
1751 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1758 __PACKAGE__-
>register_method({
1760 path
=> '{vmid}/vncproxy',
1764 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1766 description
=> "Creates a TCP VNC proxy connections.",
1768 additionalProperties
=> 0,
1770 node
=> get_standard_option
('pve-node'),
1771 vmid
=> get_standard_option
('pve-vmid'),
1775 description
=> "starts websockify instead of vncproxy",
1777 'generate-password' => {
1781 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1786 additionalProperties
=> 0,
1788 user
=> { type
=> 'string' },
1789 ticket
=> { type
=> 'string' },
1792 description
=> "Returned if requested with 'generate-password' param."
1793 ." Consists of printable ASCII characters ('!' .. '~').",
1796 cert
=> { type
=> 'string' },
1797 port
=> { type
=> 'integer' },
1798 upid
=> { type
=> 'string' },
1804 my $rpcenv = PVE
::RPCEnvironment
::get
();
1806 my $authuser = $rpcenv->get_user();
1808 my $vmid = $param->{vmid
};
1809 my $node = $param->{node
};
1810 my $websocket = $param->{websocket
};
1812 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1816 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1817 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1820 my $authpath = "/vms/$vmid";
1822 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1823 my $password = $ticket;
1824 if ($param->{'generate-password'}) {
1825 $password = $gen_rand_chars->(8);
1828 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1834 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1835 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1836 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1837 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1838 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1840 $family = PVE
::Tools
::get_host_address_family
($node);
1843 my $port = PVE
::Tools
::next_vnc_port
($family);
1850 syslog
('info', "starting vnc proxy $upid\n");
1854 if (defined($serial)) {
1856 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1858 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1859 '-timeout', $timeout, '-authpath', $authpath,
1860 '-perm', 'Sys.Console'];
1862 if ($param->{websocket
}) {
1863 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1864 push @$cmd, '-notls', '-listen', 'localhost';
1867 push @$cmd, '-c', @$remcmd, @$termcmd;
1869 PVE
::Tools
::run_command
($cmd);
1873 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1875 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1877 my $sock = IO
::Socket
::IP-
>new(
1882 GetAddrInfoFlags
=> 0,
1883 ) or die "failed to create socket: $!\n";
1884 # Inside the worker we shouldn't have any previous alarms
1885 # running anyway...:
1887 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1889 accept(my $cli, $sock) or die "connection failed: $!\n";
1892 if (PVE
::Tools
::run_command
($cmd,
1893 output
=> '>&'.fileno($cli),
1894 input
=> '<&'.fileno($cli),
1897 die "Failed to run vncproxy.\n";
1904 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1906 PVE
::Tools
::wait_for_vnc_port
($port);
1915 $res->{password
} = $password if $param->{'generate-password'};
1920 __PACKAGE__-
>register_method({
1921 name
=> 'termproxy',
1922 path
=> '{vmid}/termproxy',
1926 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1928 description
=> "Creates a TCP proxy connections.",
1930 additionalProperties
=> 0,
1932 node
=> get_standard_option
('pve-node'),
1933 vmid
=> get_standard_option
('pve-vmid'),
1937 enum
=> [qw(serial0 serial1 serial2 serial3)],
1938 description
=> "opens a serial terminal (defaults to display)",
1943 additionalProperties
=> 0,
1945 user
=> { type
=> 'string' },
1946 ticket
=> { type
=> 'string' },
1947 port
=> { type
=> 'integer' },
1948 upid
=> { type
=> 'string' },
1954 my $rpcenv = PVE
::RPCEnvironment
::get
();
1956 my $authuser = $rpcenv->get_user();
1958 my $vmid = $param->{vmid
};
1959 my $node = $param->{node
};
1960 my $serial = $param->{serial
};
1962 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1964 if (!defined($serial)) {
1966 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1967 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1971 my $authpath = "/vms/$vmid";
1973 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1978 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1979 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1980 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1981 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1982 push @$remcmd, '--';
1984 $family = PVE
::Tools
::get_host_address_family
($node);
1987 my $port = PVE
::Tools
::next_vnc_port
($family);
1989 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1990 push @$termcmd, '-iface', $serial if $serial;
1995 syslog
('info', "starting qemu termproxy $upid\n");
1997 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1998 '--perm', 'VM.Console', '--'];
1999 push @$cmd, @$remcmd, @$termcmd;
2001 PVE
::Tools
::run_command
($cmd);
2004 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2006 PVE
::Tools
::wait_for_vnc_port
($port);
2016 __PACKAGE__-
>register_method({
2017 name
=> 'vncwebsocket',
2018 path
=> '{vmid}/vncwebsocket',
2021 description
=> "You also need to pass a valid ticket (vncticket).",
2022 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2024 description
=> "Opens a weksocket for VNC traffic.",
2026 additionalProperties
=> 0,
2028 node
=> get_standard_option
('pve-node'),
2029 vmid
=> get_standard_option
('pve-vmid'),
2031 description
=> "Ticket from previous call to vncproxy.",
2036 description
=> "Port number returned by previous vncproxy call.",
2046 port
=> { type
=> 'string' },
2052 my $rpcenv = PVE
::RPCEnvironment
::get
();
2054 my $authuser = $rpcenv->get_user();
2056 my $vmid = $param->{vmid
};
2057 my $node = $param->{node
};
2059 my $authpath = "/vms/$vmid";
2061 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2063 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2065 # Note: VNC ports are acessible from outside, so we do not gain any
2066 # security if we verify that $param->{port} belongs to VM $vmid. This
2067 # check is done by verifying the VNC ticket (inside VNC protocol).
2069 my $port = $param->{port
};
2071 return { port
=> $port };
2074 __PACKAGE__-
>register_method({
2075 name
=> 'spiceproxy',
2076 path
=> '{vmid}/spiceproxy',
2081 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2083 description
=> "Returns a SPICE configuration to connect to the VM.",
2085 additionalProperties
=> 0,
2087 node
=> get_standard_option
('pve-node'),
2088 vmid
=> get_standard_option
('pve-vmid'),
2089 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2092 returns
=> get_standard_option
('remote-viewer-config'),
2096 my $rpcenv = PVE
::RPCEnvironment
::get
();
2098 my $authuser = $rpcenv->get_user();
2100 my $vmid = $param->{vmid
};
2101 my $node = $param->{node
};
2102 my $proxy = $param->{proxy
};
2104 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2105 my $title = "VM $vmid";
2106 $title .= " - ". $conf->{name
} if $conf->{name
};
2108 my $port = PVE
::QemuServer
::spice_port
($vmid);
2110 my ($ticket, undef, $remote_viewer_config) =
2111 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2113 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2114 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2116 return $remote_viewer_config;
2119 __PACKAGE__-
>register_method({
2121 path
=> '{vmid}/status',
2124 description
=> "Directory index",
2129 additionalProperties
=> 0,
2131 node
=> get_standard_option
('pve-node'),
2132 vmid
=> get_standard_option
('pve-vmid'),
2140 subdir
=> { type
=> 'string' },
2143 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2149 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2152 { subdir
=> 'current' },
2153 { subdir
=> 'start' },
2154 { subdir
=> 'stop' },
2155 { subdir
=> 'reset' },
2156 { subdir
=> 'shutdown' },
2157 { subdir
=> 'suspend' },
2158 { subdir
=> 'reboot' },
2164 __PACKAGE__-
>register_method({
2165 name
=> 'vm_status',
2166 path
=> '{vmid}/status/current',
2169 protected
=> 1, # qemu pid files are only readable by root
2170 description
=> "Get virtual machine status.",
2172 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2175 additionalProperties
=> 0,
2177 node
=> get_standard_option
('pve-node'),
2178 vmid
=> get_standard_option
('pve-vmid'),
2184 %$PVE::QemuServer
::vmstatus_return_properties
,
2186 description
=> "HA manager service status.",
2190 description
=> "Qemu VGA configuration supports spice.",
2195 description
=> "Qemu GuestAgent enabled in config.",
2205 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2207 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2208 my $status = $vmstatus->{$param->{vmid
}};
2210 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2212 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2213 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2218 __PACKAGE__-
>register_method({
2220 path
=> '{vmid}/status/start',
2224 description
=> "Start virtual machine.",
2226 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2229 additionalProperties
=> 0,
2231 node
=> get_standard_option
('pve-node'),
2232 vmid
=> get_standard_option
('pve-vmid',
2233 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2234 skiplock
=> get_standard_option
('skiplock'),
2235 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2236 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2239 enum
=> ['secure', 'insecure'],
2240 description
=> "Migration traffic is encrypted using an SSH " .
2241 "tunnel by default. On secure, completely private networks " .
2242 "this can be disabled to increase performance.",
2245 migration_network
=> {
2246 type
=> 'string', format
=> 'CIDR',
2247 description
=> "CIDR of the (sub) network that is used for migration.",
2250 machine
=> get_standard_option
('pve-qemu-machine'),
2252 description
=> "Override QEMU's -cpu argument with the given string.",
2256 targetstorage
=> get_standard_option
('pve-targetstorage'),
2258 description
=> "Wait maximal timeout seconds.",
2261 default => 'max(30, vm memory in GiB)',
2272 my $rpcenv = PVE
::RPCEnvironment
::get
();
2273 my $authuser = $rpcenv->get_user();
2275 my $node = extract_param
($param, 'node');
2276 my $vmid = extract_param
($param, 'vmid');
2277 my $timeout = extract_param
($param, 'timeout');
2279 my $machine = extract_param
($param, 'machine');
2280 my $force_cpu = extract_param
($param, 'force-cpu');
2282 my $get_root_param = sub {
2283 my $value = extract_param
($param, $_[0]);
2284 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2285 if $value && $authuser ne 'root@pam';
2289 my $stateuri = $get_root_param->('stateuri');
2290 my $skiplock = $get_root_param->('skiplock');
2291 my $migratedfrom = $get_root_param->('migratedfrom');
2292 my $migration_type = $get_root_param->('migration_type');
2293 my $migration_network = $get_root_param->('migration_network');
2294 my $targetstorage = $get_root_param->('targetstorage');
2298 if ($targetstorage) {
2299 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2301 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2302 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2306 # read spice ticket from STDIN
2308 my $nbd_protocol_version = 0;
2309 my $replicated_volumes = {};
2310 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2311 while (defined(my $line = <STDIN
>)) {
2313 if ($line =~ m/^spice_ticket: (.+)$/) {
2315 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2316 $nbd_protocol_version = $1;
2317 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2318 $replicated_volumes->{$1} = 1;
2320 # fallback for old source node
2321 $spice_ticket = $line;
2326 PVE
::Cluster
::check_cfs_quorum
();
2328 my $storecfg = PVE
::Storage
::config
();
2330 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2334 print "Requesting HA start for VM $vmid\n";
2336 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2337 PVE
::Tools
::run_command
($cmd);
2341 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2348 syslog
('info', "start VM $vmid: $upid\n");
2350 my $migrate_opts = {
2351 migratedfrom
=> $migratedfrom,
2352 spice_ticket
=> $spice_ticket,
2353 network
=> $migration_network,
2354 type
=> $migration_type,
2355 storagemap
=> $storagemap,
2356 nbd_proto_version
=> $nbd_protocol_version,
2357 replicated_volumes
=> $replicated_volumes,
2361 statefile
=> $stateuri,
2362 skiplock
=> $skiplock,
2363 forcemachine
=> $machine,
2364 timeout
=> $timeout,
2365 forcecpu
=> $force_cpu,
2368 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2372 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2376 __PACKAGE__-
>register_method({
2378 path
=> '{vmid}/status/stop',
2382 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2383 "is akin to pulling the power plug of a running computer and may damage the VM data",
2385 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2388 additionalProperties
=> 0,
2390 node
=> get_standard_option
('pve-node'),
2391 vmid
=> get_standard_option
('pve-vmid',
2392 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2393 skiplock
=> get_standard_option
('skiplock'),
2394 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2396 description
=> "Wait maximal timeout seconds.",
2402 description
=> "Do not deactivate storage volumes.",
2415 my $rpcenv = PVE
::RPCEnvironment
::get
();
2416 my $authuser = $rpcenv->get_user();
2418 my $node = extract_param
($param, 'node');
2419 my $vmid = extract_param
($param, 'vmid');
2421 my $skiplock = extract_param
($param, 'skiplock');
2422 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2423 if $skiplock && $authuser ne 'root@pam';
2425 my $keepActive = extract_param
($param, 'keepActive');
2426 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2427 if $keepActive && $authuser ne 'root@pam';
2429 my $migratedfrom = extract_param
($param, 'migratedfrom');
2430 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2431 if $migratedfrom && $authuser ne 'root@pam';
2434 my $storecfg = PVE
::Storage
::config
();
2436 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2441 print "Requesting HA stop for VM $vmid\n";
2443 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2444 PVE
::Tools
::run_command
($cmd);
2448 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2454 syslog
('info', "stop VM $vmid: $upid\n");
2456 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2457 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2461 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2465 __PACKAGE__-
>register_method({
2467 path
=> '{vmid}/status/reset',
2471 description
=> "Reset virtual machine.",
2473 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2476 additionalProperties
=> 0,
2478 node
=> get_standard_option
('pve-node'),
2479 vmid
=> get_standard_option
('pve-vmid',
2480 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2481 skiplock
=> get_standard_option
('skiplock'),
2490 my $rpcenv = PVE
::RPCEnvironment
::get
();
2492 my $authuser = $rpcenv->get_user();
2494 my $node = extract_param
($param, 'node');
2496 my $vmid = extract_param
($param, 'vmid');
2498 my $skiplock = extract_param
($param, 'skiplock');
2499 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2500 if $skiplock && $authuser ne 'root@pam';
2502 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2507 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2512 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2515 __PACKAGE__-
>register_method({
2516 name
=> 'vm_shutdown',
2517 path
=> '{vmid}/status/shutdown',
2521 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2522 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2524 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2527 additionalProperties
=> 0,
2529 node
=> get_standard_option
('pve-node'),
2530 vmid
=> get_standard_option
('pve-vmid',
2531 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2532 skiplock
=> get_standard_option
('skiplock'),
2534 description
=> "Wait maximal timeout seconds.",
2540 description
=> "Make sure the VM stops.",
2546 description
=> "Do not deactivate storage volumes.",
2559 my $rpcenv = PVE
::RPCEnvironment
::get
();
2560 my $authuser = $rpcenv->get_user();
2562 my $node = extract_param
($param, 'node');
2563 my $vmid = extract_param
($param, 'vmid');
2565 my $skiplock = extract_param
($param, 'skiplock');
2566 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2567 if $skiplock && $authuser ne 'root@pam';
2569 my $keepActive = extract_param
($param, 'keepActive');
2570 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2571 if $keepActive && $authuser ne 'root@pam';
2573 my $storecfg = PVE
::Storage
::config
();
2577 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2578 # otherwise, we will infer a shutdown command, but run into the timeout,
2579 # then when the vm is resumed, it will instantly shutdown
2581 # checking the qmp status here to get feedback to the gui/cli/api
2582 # and the status query should not take too long
2583 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2584 if ($param->{forceStop
}) {
2585 warn "VM is paused - stop instead of shutdown\n";
2588 die "VM is paused - cannot shutdown\n";
2592 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2594 my $timeout = $param->{timeout
} // 60;
2598 print "Requesting HA stop for VM $vmid\n";
2600 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2601 PVE
::Tools
::run_command
($cmd);
2605 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2612 syslog
('info', "shutdown VM $vmid: $upid\n");
2614 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2615 $shutdown, $param->{forceStop
}, $keepActive);
2619 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2623 __PACKAGE__-
>register_method({
2624 name
=> 'vm_reboot',
2625 path
=> '{vmid}/status/reboot',
2629 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2631 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2634 additionalProperties
=> 0,
2636 node
=> get_standard_option
('pve-node'),
2637 vmid
=> get_standard_option
('pve-vmid',
2638 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2640 description
=> "Wait maximal timeout seconds for the shutdown.",
2653 my $rpcenv = PVE
::RPCEnvironment
::get
();
2654 my $authuser = $rpcenv->get_user();
2656 my $node = extract_param
($param, 'node');
2657 my $vmid = extract_param
($param, 'vmid');
2659 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2661 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2666 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2667 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2671 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2674 __PACKAGE__-
>register_method({
2675 name
=> 'vm_suspend',
2676 path
=> '{vmid}/status/suspend',
2680 description
=> "Suspend virtual machine.",
2682 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2683 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2684 " on the storage for the vmstate.",
2685 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2688 additionalProperties
=> 0,
2690 node
=> get_standard_option
('pve-node'),
2691 vmid
=> get_standard_option
('pve-vmid',
2692 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2693 skiplock
=> get_standard_option
('skiplock'),
2698 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2700 statestorage
=> get_standard_option
('pve-storage-id', {
2701 description
=> "The storage for the VM state",
2702 requires
=> 'todisk',
2704 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2714 my $rpcenv = PVE
::RPCEnvironment
::get
();
2715 my $authuser = $rpcenv->get_user();
2717 my $node = extract_param
($param, 'node');
2718 my $vmid = extract_param
($param, 'vmid');
2720 my $todisk = extract_param
($param, 'todisk') // 0;
2722 my $statestorage = extract_param
($param, 'statestorage');
2724 my $skiplock = extract_param
($param, 'skiplock');
2725 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2726 if $skiplock && $authuser ne 'root@pam';
2728 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2730 die "Cannot suspend HA managed VM to disk\n"
2731 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2733 # early check for storage permission, for better user feedback
2735 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2737 if (!$statestorage) {
2738 # get statestorage from config if none is given
2739 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2740 my $storecfg = PVE
::Storage
::config
();
2741 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2744 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2750 syslog
('info', "suspend VM $vmid: $upid\n");
2752 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2757 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2758 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2761 __PACKAGE__-
>register_method({
2762 name
=> 'vm_resume',
2763 path
=> '{vmid}/status/resume',
2767 description
=> "Resume virtual machine.",
2769 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2772 additionalProperties
=> 0,
2774 node
=> get_standard_option
('pve-node'),
2775 vmid
=> get_standard_option
('pve-vmid',
2776 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2777 skiplock
=> get_standard_option
('skiplock'),
2778 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2788 my $rpcenv = PVE
::RPCEnvironment
::get
();
2790 my $authuser = $rpcenv->get_user();
2792 my $node = extract_param
($param, 'node');
2794 my $vmid = extract_param
($param, 'vmid');
2796 my $skiplock = extract_param
($param, 'skiplock');
2797 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2798 if $skiplock && $authuser ne 'root@pam';
2800 my $nocheck = extract_param
($param, 'nocheck');
2801 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2802 if $nocheck && $authuser ne 'root@pam';
2804 my $to_disk_suspended;
2806 PVE
::QemuConfig-
>lock_config($vmid, sub {
2807 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2808 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2812 die "VM $vmid not running\n"
2813 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2818 syslog
('info', "resume VM $vmid: $upid\n");
2820 if (!$to_disk_suspended) {
2821 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2823 my $storecfg = PVE
::Storage
::config
();
2824 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2830 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2833 __PACKAGE__-
>register_method({
2834 name
=> 'vm_sendkey',
2835 path
=> '{vmid}/sendkey',
2839 description
=> "Send key event to virtual machine.",
2841 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2844 additionalProperties
=> 0,
2846 node
=> get_standard_option
('pve-node'),
2847 vmid
=> get_standard_option
('pve-vmid',
2848 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2849 skiplock
=> get_standard_option
('skiplock'),
2851 description
=> "The key (qemu monitor encoding).",
2856 returns
=> { type
=> 'null'},
2860 my $rpcenv = PVE
::RPCEnvironment
::get
();
2862 my $authuser = $rpcenv->get_user();
2864 my $node = extract_param
($param, 'node');
2866 my $vmid = extract_param
($param, 'vmid');
2868 my $skiplock = extract_param
($param, 'skiplock');
2869 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2870 if $skiplock && $authuser ne 'root@pam';
2872 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2877 __PACKAGE__-
>register_method({
2878 name
=> 'vm_feature',
2879 path
=> '{vmid}/feature',
2883 description
=> "Check if feature for virtual machine is available.",
2885 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2888 additionalProperties
=> 0,
2890 node
=> get_standard_option
('pve-node'),
2891 vmid
=> get_standard_option
('pve-vmid'),
2893 description
=> "Feature to check.",
2895 enum
=> [ 'snapshot', 'clone', 'copy' ],
2897 snapname
=> get_standard_option
('pve-snapshot-name', {
2905 hasFeature
=> { type
=> 'boolean' },
2908 items
=> { type
=> 'string' },
2915 my $node = extract_param
($param, 'node');
2917 my $vmid = extract_param
($param, 'vmid');
2919 my $snapname = extract_param
($param, 'snapname');
2921 my $feature = extract_param
($param, 'feature');
2923 my $running = PVE
::QemuServer
::check_running
($vmid);
2925 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2928 my $snap = $conf->{snapshots
}->{$snapname};
2929 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2932 my $storecfg = PVE
::Storage
::config
();
2934 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2935 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2938 hasFeature
=> $hasFeature,
2939 nodes
=> [ keys %$nodelist ],
2943 __PACKAGE__-
>register_method({
2945 path
=> '{vmid}/clone',
2949 description
=> "Create a copy of virtual machine/template.",
2951 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2952 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2953 "'Datastore.AllocateSpace' on any used storage.",
2956 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2958 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2959 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2964 additionalProperties
=> 0,
2966 node
=> get_standard_option
('pve-node'),
2967 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2968 newid
=> get_standard_option
('pve-vmid', {
2969 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2970 description
=> 'VMID for the clone.' }),
2973 type
=> 'string', format
=> 'dns-name',
2974 description
=> "Set a name for the new VM.",
2979 description
=> "Description for the new VM.",
2983 type
=> 'string', format
=> 'pve-poolid',
2984 description
=> "Add the new VM to the specified pool.",
2986 snapname
=> get_standard_option
('pve-snapshot-name', {
2989 storage
=> get_standard_option
('pve-storage-id', {
2990 description
=> "Target storage for full clone.",
2994 description
=> "Target format for file storage. Only valid for full clone.",
2997 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3002 description
=> "Create a full copy of all disks. This is always done when " .
3003 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3005 target
=> get_standard_option
('pve-node', {
3006 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3010 description
=> "Override I/O bandwidth limit (in KiB/s).",
3014 default => 'clone limit from datacenter or storage config',
3024 my $rpcenv = PVE
::RPCEnvironment
::get
();
3025 my $authuser = $rpcenv->get_user();
3027 my $node = extract_param
($param, 'node');
3028 my $vmid = extract_param
($param, 'vmid');
3029 my $newid = extract_param
($param, 'newid');
3030 my $pool = extract_param
($param, 'pool');
3031 $rpcenv->check_pool_exist($pool) if defined($pool);
3033 my $snapname = extract_param
($param, 'snapname');
3034 my $storage = extract_param
($param, 'storage');
3035 my $format = extract_param
($param, 'format');
3036 my $target = extract_param
($param, 'target');
3038 my $localnode = PVE
::INotify
::nodename
();
3040 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3044 PVE
::Cluster
::check_node_exists
($target) if $target;
3046 my $storecfg = PVE
::Storage
::config
();
3049 # check if storage is enabled on local node
3050 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3052 # check if storage is available on target node
3053 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3054 # clone only works if target storage is shared
3055 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3056 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3060 PVE
::Cluster
::check_cfs_quorum
();
3062 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3065 # do all tests after lock but before forking worker - if possible
3067 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3068 PVE
::QemuConfig-
>check_lock($conf);
3070 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3071 die "unexpected state change\n" if $verify_running != $running;
3073 die "snapshot '$snapname' does not exist\n"
3074 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3076 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3078 die "parameter 'storage' not allowed for linked clones\n"
3079 if defined($storage) && !$full;
3081 die "parameter 'format' not allowed for linked clones\n"
3082 if defined($format) && !$full;
3084 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3086 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3088 die "can't clone VM to node '$target' (VM uses local storage)\n"
3089 if $target && !$sharedvm;
3091 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3092 die "unable to create VM $newid: config file already exists\n"
3095 my $newconf = { lock => 'clone' };
3100 foreach my $opt (keys %$oldconf) {
3101 my $value = $oldconf->{$opt};
3103 # do not copy snapshot related info
3104 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3105 $opt eq 'vmstate' || $opt eq 'snapstate';
3107 # no need to copy unused images, because VMID(owner) changes anyways
3108 next if $opt =~ m/^unused\d+$/;
3110 # always change MAC! address
3111 if ($opt =~ m/^net(\d+)$/) {
3112 my $net = PVE
::QemuServer
::parse_net
($value);
3113 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3114 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3115 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3116 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3117 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3118 die "unable to parse drive options for '$opt'\n" if !$drive;
3119 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3120 $newconf->{$opt} = $value; # simply copy configuration
3122 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3123 die "Full clone feature is not supported for drive '$opt'\n"
3124 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3125 $fullclone->{$opt} = 1;
3127 # not full means clone instead of copy
3128 die "Linked clone feature is not supported for drive '$opt'\n"
3129 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3131 $drives->{$opt} = $drive;
3132 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3133 push @$vollist, $drive->{file
};
3136 # copy everything else
3137 $newconf->{$opt} = $value;
3141 # auto generate a new uuid
3142 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3143 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3144 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3145 # auto generate a new vmgenid only if the option was set for template
3146 if ($newconf->{vmgenid
}) {
3147 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3150 delete $newconf->{template
};
3152 if ($param->{name
}) {
3153 $newconf->{name
} = $param->{name
};
3155 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3158 if ($param->{description
}) {
3159 $newconf->{description
} = $param->{description
};
3162 # create empty/temp config - this fails if VM already exists on other node
3163 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3164 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3169 my $newvollist = [];
3176 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3178 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3180 my $bwlimit = extract_param
($param, 'bwlimit');
3182 my $total_jobs = scalar(keys %{$drives});
3185 foreach my $opt (sort keys %$drives) {
3186 my $drive = $drives->{$opt};
3187 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3188 my $completion = $skipcomplete ?
'skip' : 'complete';
3190 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3191 my $storage_list = [ $src_sid ];
3192 push @$storage_list, $storage if defined($storage);
3193 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3195 my $newdrive = PVE
::QemuServer
::clone_disk
(
3214 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3216 PVE
::QemuConfig-
>write_config($newid, $newconf);
3220 delete $newconf->{lock};
3222 # do not write pending changes
3223 if (my @changes = keys %{$newconf->{pending
}}) {
3224 my $pending = join(',', @changes);
3225 warn "found pending changes for '$pending', discarding for clone\n";
3226 delete $newconf->{pending
};
3229 PVE
::QemuConfig-
>write_config($newid, $newconf);
3232 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3233 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3234 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3236 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3237 die "Failed to move config to node '$target' - rename failed: $!\n"
3238 if !rename($conffile, $newconffile);
3241 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3244 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3245 sleep 1; # some storage like rbd need to wait before release volume - really?
3247 foreach my $volid (@$newvollist) {
3248 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3252 PVE
::Firewall
::remove_vmfw_conf
($newid);
3254 unlink $conffile; # avoid races -> last thing before die
3256 die "clone failed: $err";
3262 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3264 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3267 # Aquire exclusive lock lock for $newid
3268 my $lock_target_vm = sub {
3269 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3272 # exclusive lock if VM is running - else shared lock is enough;
3274 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3276 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3280 __PACKAGE__-
>register_method({
3281 name
=> 'move_vm_disk',
3282 path
=> '{vmid}/move_disk',
3286 description
=> "Move volume to different storage or to a different VM.",
3288 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3289 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3290 "a disk to another VM, you need the permissions on the target VM as well.",
3291 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3294 additionalProperties
=> 0,
3296 node
=> get_standard_option
('pve-node'),
3297 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3298 'target-vmid' => get_standard_option
('pve-vmid', {
3299 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3304 description
=> "The disk you want to move.",
3305 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3307 storage
=> get_standard_option
('pve-storage-id', {
3308 description
=> "Target storage.",
3309 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3314 description
=> "Target Format.",
3315 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3320 description
=> "Delete the original disk after successful copy. By default the " .
3321 "original disk is kept as unused disk.",
3327 description
=> 'Prevent changes if current configuration file has different SHA1 " .
3328 "digest. This can be used to prevent concurrent modifications.',
3333 description
=> "Override I/O bandwidth limit (in KiB/s).",
3337 default => 'move limit from datacenter or storage config',
3341 description
=> "The config key the disk will be moved to on the target VM " .
3342 "(for example, ide0 or scsi1). Default is the source disk key.",
3343 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3346 'target-digest' => {
3348 description
=> 'Prevent changes if current configuration file of the target VM has " .
3349 "a different SHA1 digest. This can be used to prevent concurrent modifications.',
3357 description
=> "the task ID.",
3362 my $rpcenv = PVE
::RPCEnvironment
::get
();
3363 my $authuser = $rpcenv->get_user();
3365 my $node = extract_param
($param, 'node');
3366 my $vmid = extract_param
($param, 'vmid');
3367 my $target_vmid = extract_param
($param, 'target-vmid');
3368 my $digest = extract_param
($param, 'digest');
3369 my $target_digest = extract_param
($param, 'target-digest');
3370 my $disk = extract_param
($param, 'disk');
3371 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3372 my $storeid = extract_param
($param, 'storage');
3373 my $format = extract_param
($param, 'format');
3375 my $storecfg = PVE
::Storage
::config
();
3377 my $move_updatefn = sub {
3378 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3379 PVE
::QemuConfig-
>check_lock($conf);
3381 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3383 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3385 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3387 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3388 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3390 my $old_volid = $drive->{file
};
3392 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3393 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3397 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3398 (!$format || !$oldfmt || $oldfmt eq $format);
3400 # this only checks snapshots because $disk is passed!
3401 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3407 die "you can't move a disk with snapshots and delete the source\n"
3408 if $snapshotted && $param->{delete};
3410 PVE
::Cluster
::log_msg
(
3413 "move disk VM $vmid: move --disk $disk --storage $storeid"
3416 my $running = PVE
::QemuServer
::check_running
($vmid);
3418 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3421 my $newvollist = [];
3427 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3429 warn "moving disk with snapshots, snapshots will not be moved!\n"
3432 my $bwlimit = extract_param
($param, 'bwlimit');
3433 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3435 [$oldstoreid, $storeid],
3439 my $newdrive = PVE
::QemuServer
::clone_disk
(
3457 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3459 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3461 # convert moved disk to base if part of template
3462 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3463 if PVE
::QemuConfig-
>is_template($conf);
3465 PVE
::QemuConfig-
>write_config($vmid, $conf);
3467 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3468 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3469 eval { mon_cmd
($vmid, "guest-fstrim") };
3473 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3474 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3480 foreach my $volid (@$newvollist) {
3481 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3484 die "storage migration failed: $err";
3487 if ($param->{delete}) {
3489 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3490 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3496 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3499 my $load_and_check_reassign_configs = sub {
3500 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
3502 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
3503 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
3505 my $source_node = $vmlist->{$vmid}->{node
};
3506 my $target_node = $vmlist->{$target_vmid}->{node
};
3508 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
3509 if $source_node ne $target_node;
3511 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
3512 PVE
::QemuConfig-
>check_lock($source_conf);
3513 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
3514 PVE
::QemuConfig-
>check_lock($target_conf);
3516 die "Can't move disks from or to template VMs\n"
3517 if ($source_conf->{template
} || $target_conf->{template
});
3520 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
3521 die "VM ${vmid}: $@" if $@;
3524 if ($target_digest) {
3525 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
3526 die "VM ${target_vmid}: $@" if $@;
3529 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
3531 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
3532 if $target_conf->{$target_disk};
3534 my $drive = PVE
::QemuServer
::parse_drive
(
3536 $source_conf->{$disk},
3538 die "failed to parse source disk - $@\n" if !$drive;
3540 my $source_volid = $drive->{file
};
3542 die "disk '${disk}' has no associated volume\n" if !$source_volid;
3543 die "CD drive contents can't be moved to another VM\n"
3544 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3546 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
3547 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
3549 die "Can't move disk used by a snapshot to another VM\n"
3550 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
3551 die "Storage does not support moving of this disk to another VM\n"
3552 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
3553 die "Cannot move disk to another VM while the source VM is running - detach first\n"
3554 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
3556 # now re-parse using target disk slot format
3557 if ($target_disk =~ /^unused\d+$/) {
3558 $drive = PVE
::QemuServer
::parse_drive
(
3563 $drive = PVE
::QemuServer
::parse_drive
(
3565 $source_conf->{$disk},
3568 die "failed to parse source disk for target disk format - $@\n" if !$drive;
3570 my $repl_conf = PVE
::ReplicationConfig-
>new();
3571 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
3572 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3573 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
3574 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
3577 return ($source_conf, $target_conf, $drive);
3582 print STDERR
"$msg\n";
3585 my $disk_reassignfn = sub {
3586 return PVE
::QemuConfig-
>lock_config($vmid, sub {
3587 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
3588 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
3590 my $source_volid = $drive->{file
};
3592 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
3593 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
3595 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3597 my $new_volid = PVE
::Storage
::rename_volume
(
3603 $drive->{file
} = $new_volid;
3605 delete $source_conf->{$disk};
3606 print "removing disk '${disk}' from VM '${vmid}' config\n";
3607 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
3609 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
3613 vmid
=> $target_vmid,
3614 digest
=> $target_digest,
3615 $target_disk => $drive_string,
3620 # remove possible replication snapshots
3621 if (PVE
::Storage
::volume_has_feature
(
3627 PVE
::Replication
::prepare
(
3637 print "Failed to remove replication snapshots on moved disk " .
3638 "'$target_disk'. Manual cleanup could be necessary.\n";
3645 if ($target_vmid && $storeid) {
3646 my $msg = "either set 'storage' or 'target-vmid', but not both";
3647 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3648 } elsif ($target_vmid) {
3649 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
3650 if $authuser ne 'root@pam';
3652 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
3653 if $vmid eq $target_vmid;
3655 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
3656 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
3657 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
3659 return $rpcenv->fork_worker(
3661 "${vmid}-${disk}>${target_vmid}-${target_disk}",
3665 } elsif ($storeid) {
3666 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3668 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
3669 if $disk =~ m/^unused\d+$/;
3670 return PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
3672 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
3673 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3677 my $check_vm_disks_local = sub {
3678 my ($storecfg, $vmconf, $vmid) = @_;
3680 my $local_disks = {};
3682 # add some more information to the disks e.g. cdrom
3683 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3684 my ($volid, $attr) = @_;
3686 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3688 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3689 return if $scfg->{shared
};
3691 # The shared attr here is just a special case where the vdisk
3692 # is marked as shared manually
3693 return if $attr->{shared
};
3694 return if $attr->{cdrom
} and $volid eq "none";
3696 if (exists $local_disks->{$volid}) {
3697 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3699 $local_disks->{$volid} = $attr;
3700 # ensure volid is present in case it's needed
3701 $local_disks->{$volid}->{volid
} = $volid;
3705 return $local_disks;
3708 __PACKAGE__-
>register_method({
3709 name
=> 'migrate_vm_precondition',
3710 path
=> '{vmid}/migrate',
3714 description
=> "Get preconditions for migration.",
3716 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3719 additionalProperties
=> 0,
3721 node
=> get_standard_option
('pve-node'),
3722 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3723 target
=> get_standard_option
('pve-node', {
3724 description
=> "Target node.",
3725 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3733 running
=> { type
=> 'boolean' },
3737 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3739 not_allowed_nodes
=> {
3742 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3746 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3748 local_resources
=> {
3750 description
=> "List local resources e.g. pci, usb"
3757 my $rpcenv = PVE
::RPCEnvironment
::get
();
3759 my $authuser = $rpcenv->get_user();
3761 PVE
::Cluster
::check_cfs_quorum
();
3765 my $vmid = extract_param
($param, 'vmid');
3766 my $target = extract_param
($param, 'target');
3767 my $localnode = PVE
::INotify
::nodename
();
3771 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3772 my $storecfg = PVE
::Storage
::config
();
3775 # try to detect errors early
3776 PVE
::QemuConfig-
>check_lock($vmconf);
3778 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3780 # if vm is not running, return target nodes where local storage is available
3781 # for offline migration
3782 if (!$res->{running
}) {
3783 $res->{allowed_nodes
} = [];
3784 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3785 delete $checked_nodes->{$localnode};
3787 foreach my $node (keys %$checked_nodes) {
3788 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3789 push @{$res->{allowed_nodes
}}, $node;
3793 $res->{not_allowed_nodes
} = $checked_nodes;
3797 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3798 $res->{local_disks
} = [ values %$local_disks ];;
3800 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3802 $res->{local_resources
} = $local_resources;
3809 __PACKAGE__-
>register_method({
3810 name
=> 'migrate_vm',
3811 path
=> '{vmid}/migrate',
3815 description
=> "Migrate virtual machine. Creates a new migration task.",
3817 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3820 additionalProperties
=> 0,
3822 node
=> get_standard_option
('pve-node'),
3823 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3824 target
=> get_standard_option
('pve-node', {
3825 description
=> "Target node.",
3826 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3830 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3835 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3840 enum
=> ['secure', 'insecure'],
3841 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3844 migration_network
=> {
3845 type
=> 'string', format
=> 'CIDR',
3846 description
=> "CIDR of the (sub) network that is used for migration.",
3849 "with-local-disks" => {
3851 description
=> "Enable live storage migration for local disk",
3854 targetstorage
=> get_standard_option
('pve-targetstorage', {
3855 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3858 description
=> "Override I/O bandwidth limit (in KiB/s).",
3862 default => 'migrate limit from datacenter or storage config',
3868 description
=> "the task ID.",
3873 my $rpcenv = PVE
::RPCEnvironment
::get
();
3874 my $authuser = $rpcenv->get_user();
3876 my $target = extract_param
($param, 'target');
3878 my $localnode = PVE
::INotify
::nodename
();
3879 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3881 PVE
::Cluster
::check_cfs_quorum
();
3883 PVE
::Cluster
::check_node_exists
($target);
3885 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3887 my $vmid = extract_param
($param, 'vmid');
3889 raise_param_exc
({ force
=> "Only root may use this option." })
3890 if $param->{force
} && $authuser ne 'root@pam';
3892 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3893 if $param->{migration_type
} && $authuser ne 'root@pam';
3895 # allow root only until better network permissions are available
3896 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3897 if $param->{migration_network
} && $authuser ne 'root@pam';
3900 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3902 # try to detect errors early
3904 PVE
::QemuConfig-
>check_lock($conf);
3906 if (PVE
::QemuServer
::check_running
($vmid)) {
3907 die "can't migrate running VM without --online\n" if !$param->{online
};
3909 my $repl_conf = PVE
::ReplicationConfig-
>new();
3910 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3911 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3912 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3913 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3914 "target. Use 'force' to override.\n";
3917 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3918 $param->{online
} = 0;
3921 my $storecfg = PVE
::Storage
::config
();
3923 if (my $targetstorage = $param->{targetstorage
}) {
3924 my $check_storage = sub {
3925 my ($target_sid) = @_;
3926 PVE
::Storage
::storage_check_enabled
($storecfg, $target_sid, $target);
3927 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3928 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3929 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3930 if !$scfg->{content
}->{images
};
3933 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3934 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3937 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3938 if !defined($storagemap->{identity
});
3940 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3941 $check_storage->($target_sid);
3944 $check_storage->($storagemap->{default})
3945 if $storagemap->{default};
3947 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3948 if $storagemap->{identity
};
3950 $param->{storagemap
} = $storagemap;
3952 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3955 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3960 print "Requesting HA migration for VM $vmid to node $target\n";
3962 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3963 PVE
::Tools
::run_command
($cmd);
3967 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3972 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3976 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3979 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3984 __PACKAGE__-
>register_method({
3986 path
=> '{vmid}/monitor',
3990 description
=> "Execute Qemu monitor commands.",
3992 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3993 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3996 additionalProperties
=> 0,
3998 node
=> get_standard_option
('pve-node'),
3999 vmid
=> get_standard_option
('pve-vmid'),
4002 description
=> "The monitor command.",
4006 returns
=> { type
=> 'string'},
4010 my $rpcenv = PVE
::RPCEnvironment
::get
();
4011 my $authuser = $rpcenv->get_user();
4014 my $command = shift;
4015 return $command =~ m/^\s*info(\s+|$)/
4016 || $command =~ m/^\s*help\s*$/;
4019 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4020 if !&$is_ro($param->{command
});
4022 my $vmid = $param->{vmid
};
4024 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4028 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4030 $res = "ERROR: $@" if $@;
4035 __PACKAGE__-
>register_method({
4036 name
=> 'resize_vm',
4037 path
=> '{vmid}/resize',
4041 description
=> "Extend volume size.",
4043 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4046 additionalProperties
=> 0,
4048 node
=> get_standard_option
('pve-node'),
4049 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4050 skiplock
=> get_standard_option
('skiplock'),
4053 description
=> "The disk you want to resize.",
4054 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4058 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4059 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.",
4063 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4069 returns
=> { type
=> 'null'},
4073 my $rpcenv = PVE
::RPCEnvironment
::get
();
4075 my $authuser = $rpcenv->get_user();
4077 my $node = extract_param
($param, 'node');
4079 my $vmid = extract_param
($param, 'vmid');
4081 my $digest = extract_param
($param, 'digest');
4083 my $disk = extract_param
($param, 'disk');
4085 my $sizestr = extract_param
($param, 'size');
4087 my $skiplock = extract_param
($param, 'skiplock');
4088 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4089 if $skiplock && $authuser ne 'root@pam';
4091 my $storecfg = PVE
::Storage
::config
();
4093 my $updatefn = sub {
4095 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4097 die "checksum missmatch (file change by other user?)\n"
4098 if $digest && $digest ne $conf->{digest
};
4099 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4101 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4103 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4105 my (undef, undef, undef, undef, undef, undef, $format) =
4106 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4108 die "can't resize volume: $disk if snapshot exists\n"
4109 if %{$conf->{snapshots
}} && $format eq 'qcow2';
4111 my $volid = $drive->{file
};
4113 die "disk '$disk' has no associated volume\n" if !$volid;
4115 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4117 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4119 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4121 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4122 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4124 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4126 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4127 my ($ext, $newsize, $unit) = ($1, $2, $4);
4130 $newsize = $newsize * 1024;
4131 } elsif ($unit eq 'M') {
4132 $newsize = $newsize * 1024 * 1024;
4133 } elsif ($unit eq 'G') {
4134 $newsize = $newsize * 1024 * 1024 * 1024;
4135 } elsif ($unit eq 'T') {
4136 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4139 $newsize += $size if $ext;
4140 $newsize = int($newsize);
4142 die "shrinking disks is not supported\n" if $newsize < $size;
4144 return if $size == $newsize;
4146 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4148 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4150 $drive->{size
} = $newsize;
4151 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4153 PVE
::QemuConfig-
>write_config($vmid, $conf);
4156 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4160 __PACKAGE__-
>register_method({
4161 name
=> 'snapshot_list',
4162 path
=> '{vmid}/snapshot',
4164 description
=> "List all snapshots.",
4166 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4169 protected
=> 1, # qemu pid files are only readable by root
4171 additionalProperties
=> 0,
4173 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4174 node
=> get_standard_option
('pve-node'),
4183 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4187 description
=> "Snapshot includes RAM.",
4192 description
=> "Snapshot description.",
4196 description
=> "Snapshot creation time",
4198 renderer
=> 'timestamp',
4202 description
=> "Parent snapshot identifier.",
4208 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4213 my $vmid = $param->{vmid
};
4215 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4216 my $snaphash = $conf->{snapshots
} || {};
4220 foreach my $name (keys %$snaphash) {
4221 my $d = $snaphash->{$name};
4224 snaptime
=> $d->{snaptime
} || 0,
4225 vmstate
=> $d->{vmstate
} ?
1 : 0,
4226 description
=> $d->{description
} || '',
4228 $item->{parent
} = $d->{parent
} if $d->{parent
};
4229 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4233 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4236 digest
=> $conf->{digest
},
4237 running
=> $running,
4238 description
=> "You are here!",
4240 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4242 push @$res, $current;
4247 __PACKAGE__-
>register_method({
4249 path
=> '{vmid}/snapshot',
4253 description
=> "Snapshot a VM.",
4255 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4258 additionalProperties
=> 0,
4260 node
=> get_standard_option
('pve-node'),
4261 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4262 snapname
=> get_standard_option
('pve-snapshot-name'),
4266 description
=> "Save the vmstate",
4271 description
=> "A textual description or comment.",
4277 description
=> "the task ID.",
4282 my $rpcenv = PVE
::RPCEnvironment
::get
();
4284 my $authuser = $rpcenv->get_user();
4286 my $node = extract_param
($param, 'node');
4288 my $vmid = extract_param
($param, 'vmid');
4290 my $snapname = extract_param
($param, 'snapname');
4292 die "unable to use snapshot name 'current' (reserved name)\n"
4293 if $snapname eq 'current';
4295 die "unable to use snapshot name 'pending' (reserved name)\n"
4296 if lc($snapname) eq 'pending';
4299 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4300 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4301 $param->{description
});
4304 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4307 __PACKAGE__-
>register_method({
4308 name
=> 'snapshot_cmd_idx',
4309 path
=> '{vmid}/snapshot/{snapname}',
4316 additionalProperties
=> 0,
4318 vmid
=> get_standard_option
('pve-vmid'),
4319 node
=> get_standard_option
('pve-node'),
4320 snapname
=> get_standard_option
('pve-snapshot-name'),
4329 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4336 push @$res, { cmd
=> 'rollback' };
4337 push @$res, { cmd
=> 'config' };
4342 __PACKAGE__-
>register_method({
4343 name
=> 'update_snapshot_config',
4344 path
=> '{vmid}/snapshot/{snapname}/config',
4348 description
=> "Update snapshot metadata.",
4350 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4353 additionalProperties
=> 0,
4355 node
=> get_standard_option
('pve-node'),
4356 vmid
=> get_standard_option
('pve-vmid'),
4357 snapname
=> get_standard_option
('pve-snapshot-name'),
4361 description
=> "A textual description or comment.",
4365 returns
=> { type
=> 'null' },
4369 my $rpcenv = PVE
::RPCEnvironment
::get
();
4371 my $authuser = $rpcenv->get_user();
4373 my $vmid = extract_param
($param, 'vmid');
4375 my $snapname = extract_param
($param, 'snapname');
4377 return if !defined($param->{description
});
4379 my $updatefn = sub {
4381 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4383 PVE
::QemuConfig-
>check_lock($conf);
4385 my $snap = $conf->{snapshots
}->{$snapname};
4387 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4389 $snap->{description
} = $param->{description
} if defined($param->{description
});
4391 PVE
::QemuConfig-
>write_config($vmid, $conf);
4394 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4399 __PACKAGE__-
>register_method({
4400 name
=> 'get_snapshot_config',
4401 path
=> '{vmid}/snapshot/{snapname}/config',
4404 description
=> "Get snapshot configuration",
4406 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4409 additionalProperties
=> 0,
4411 node
=> get_standard_option
('pve-node'),
4412 vmid
=> get_standard_option
('pve-vmid'),
4413 snapname
=> get_standard_option
('pve-snapshot-name'),
4416 returns
=> { type
=> "object" },
4420 my $rpcenv = PVE
::RPCEnvironment
::get
();
4422 my $authuser = $rpcenv->get_user();
4424 my $vmid = extract_param
($param, 'vmid');
4426 my $snapname = extract_param
($param, 'snapname');
4428 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4430 my $snap = $conf->{snapshots
}->{$snapname};
4432 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4437 __PACKAGE__-
>register_method({
4439 path
=> '{vmid}/snapshot/{snapname}/rollback',
4443 description
=> "Rollback VM state to specified snapshot.",
4445 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4448 additionalProperties
=> 0,
4450 node
=> get_standard_option
('pve-node'),
4451 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4452 snapname
=> get_standard_option
('pve-snapshot-name'),
4457 description
=> "the task ID.",
4462 my $rpcenv = PVE
::RPCEnvironment
::get
();
4464 my $authuser = $rpcenv->get_user();
4466 my $node = extract_param
($param, 'node');
4468 my $vmid = extract_param
($param, 'vmid');
4470 my $snapname = extract_param
($param, 'snapname');
4473 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4474 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4478 # hold migration lock, this makes sure that nobody create replication snapshots
4479 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4482 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4485 __PACKAGE__-
>register_method({
4486 name
=> 'delsnapshot',
4487 path
=> '{vmid}/snapshot/{snapname}',
4491 description
=> "Delete a VM snapshot.",
4493 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4496 additionalProperties
=> 0,
4498 node
=> get_standard_option
('pve-node'),
4499 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4500 snapname
=> get_standard_option
('pve-snapshot-name'),
4504 description
=> "For removal from config file, even if removing disk snapshots fails.",
4510 description
=> "the task ID.",
4515 my $rpcenv = PVE
::RPCEnvironment
::get
();
4517 my $authuser = $rpcenv->get_user();
4519 my $node = extract_param
($param, 'node');
4521 my $vmid = extract_param
($param, 'vmid');
4523 my $snapname = extract_param
($param, 'snapname');
4526 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4527 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4530 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4533 __PACKAGE__-
>register_method({
4535 path
=> '{vmid}/template',
4539 description
=> "Create a Template.",
4541 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4542 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4545 additionalProperties
=> 0,
4547 node
=> get_standard_option
('pve-node'),
4548 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4552 description
=> "If you want to convert only 1 disk to base image.",
4553 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4560 description
=> "the task ID.",
4565 my $rpcenv = PVE
::RPCEnvironment
::get
();
4567 my $authuser = $rpcenv->get_user();
4569 my $node = extract_param
($param, 'node');
4571 my $vmid = extract_param
($param, 'vmid');
4573 my $disk = extract_param
($param, 'disk');
4575 my $load_and_check = sub {
4576 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4578 PVE
::QemuConfig-
>check_lock($conf);
4580 die "unable to create template, because VM contains snapshots\n"
4581 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4583 die "you can't convert a template to a template\n"
4584 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4586 die "you can't convert a VM to template if VM is running\n"
4587 if PVE
::QemuServer
::check_running
($vmid);
4592 $load_and_check->();
4595 PVE
::QemuConfig-
>lock_config($vmid, sub {
4596 my $conf = $load_and_check->();
4598 $conf->{template
} = 1;
4599 PVE
::QemuConfig-
>write_config($vmid, $conf);
4601 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4605 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4608 __PACKAGE__-
>register_method({
4609 name
=> 'cloudinit_generated_config_dump',
4610 path
=> '{vmid}/cloudinit/dump',
4613 description
=> "Get automatically generated cloudinit config.",
4615 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4618 additionalProperties
=> 0,
4620 node
=> get_standard_option
('pve-node'),
4621 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4623 description
=> 'Config type.',
4625 enum
=> ['user', 'network', 'meta'],
4635 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4637 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});