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);
28 use PVE
::RPCEnvironment
;
29 use PVE
::AccessControl
;
33 use PVE
::API2
::Firewall
::VM
;
34 use PVE
::API2
::Qemu
::Agent
;
35 use PVE
::VZDump
::Plugin
;
36 use PVE
::DataCenterConfig
;
40 if (!$ENV{PVE_GENERATING_DOCS
}) {
41 require PVE
::HA
::Env
::PVE2
;
42 import PVE
::HA
::Env
::PVE2
;
43 require PVE
::HA
::Config
;
44 import PVE
::HA
::Config
;
48 use Data
::Dumper
; # fixme: remove
50 use base
qw(PVE::RESTHandler);
52 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.";
54 my $resolve_cdrom_alias = sub {
57 if (my $value = $param->{cdrom
}) {
58 $value .= ",media=cdrom" if $value !~ m/media=/;
59 $param->{ide2
} = $value;
60 delete $param->{cdrom
};
64 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
65 my $check_storage_access = sub {
66 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
68 PVE
::QemuConfig-
>foreach_volume($settings, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
74 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
76 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
78 } elsif ($isCDROM && ($volid eq 'cdrom')) {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
81 my ($storeid, $size) = ($2 || $default_storage, $3);
82 die "no storage ID specified (and no default storage)\n" if !$storeid;
83 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
85 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
86 if !$scfg->{content
}->{images
};
88 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
92 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
93 if defined($settings->{vmstatestorage
});
96 my $check_storage_access_clone = sub {
97 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
101 PVE
::QemuConfig-
>foreach_volume($conf, sub {
102 my ($ds, $drive) = @_;
104 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
106 my $volid = $drive->{file
};
108 return if !$volid || $volid eq 'none';
111 if ($volid eq 'cdrom') {
112 $rpcenv->check($authuser, "/", ['Sys.Console']);
114 # we simply allow access
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
121 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
122 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
123 $sharedvm = 0 if !$scfg->{shared
};
125 $sid = $storage if $storage;
126 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
130 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
131 if defined($conf->{vmstatestorage
});
136 # Note: $pool is only needed when creating a VM, because pool permissions
137 # are automatically inherited if VM already exists inside a pool.
138 my $create_disks = sub {
139 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
146 my ($ds, $disk) = @_;
148 my $volid = $disk->{file
};
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
151 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
152 delete $disk->{size
};
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
154 } elsif (defined($volname) && $volname eq 'cloudinit') {
155 $storeid = $storeid // $default_storage;
156 die "no storage ID specified (and no default storage)\n" if !$storeid;
157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
158 my $name = "vm-$vmid-cloudinit";
162 $fmt = $disk->{format
} // "qcow2";
165 $fmt = $disk->{format
} // "raw";
168 # Initial disk created with 4 MB and aligned to 4MB on regeneration
169 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
170 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
171 $disk->{file
} = $volid;
172 $disk->{media
} = 'cdrom';
173 push @$vollist, $volid;
174 delete $disk->{format
}; # no longer needed
175 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
176 } elsif ($volid =~ $NEW_DISK_RE) {
177 my ($storeid, $size) = ($2 || $default_storage, $3);
178 die "no storage ID specified (and no default storage)\n" if !$storeid;
179 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
180 my $fmt = $disk->{format
} || $defformat;
182 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
185 if ($ds eq 'efidisk0') {
186 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
187 $storecfg, $storeid, $vmid, $fmt, $arch, $disk);
188 } elsif ($ds eq 'tpmstate0') {
189 # swtpm can only use raw volumes, and uses a fixed size
190 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
191 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
193 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
195 push @$vollist, $volid;
196 $disk->{file
} = $volid;
197 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
198 delete $disk->{format
}; # no longer needed
199 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
202 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
204 my $volid_is_new = 1;
207 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
208 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
213 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
215 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
217 die "volume $volid does not exist\n" if !$size;
219 $disk->{size
} = $size;
222 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
226 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
228 # free allocated images on error
230 syslog
('err', "VM $vmid creating disks failed");
231 foreach my $volid (@$vollist) {
232 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
238 # modify vm config if everything went well
239 foreach my $ds (keys %$res) {
240 $conf->{$ds} = $res->{$ds};
246 my $check_cpu_model_access = sub {
247 my ($rpcenv, $authuser, $new, $existing) = @_;
249 return if !defined($new->{cpu
});
251 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
252 return if !$cpu || !$cpu->{cputype
}; # always allow default
253 my $cputype = $cpu->{cputype
};
255 if ($existing && $existing->{cpu
}) {
256 # changing only other settings doesn't require permissions for CPU model
257 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
258 return if $existingCpu->{cputype
} eq $cputype;
261 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
262 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
277 my $memoryoptions = {
283 my $hwtypeoptions = {
296 my $generaloptions = {
303 'migrate_downtime' => 1,
304 'migrate_speed' => 1,
317 my $vmpoweroptions = {
324 'vmstatestorage' => 1,
327 my $cloudinitoptions = {
337 my $check_vm_create_serial_perm = sub {
338 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
340 return 1 if $authuser eq 'root@pam';
342 foreach my $opt (keys %{$param}) {
343 next if $opt !~ m/^serial\d+$/;
345 if ($param->{$opt} eq 'socket') {
346 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
348 die "only root can set '$opt' config for real devices\n";
355 my $check_vm_create_usb_perm = sub {
356 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
358 return 1 if $authuser eq 'root@pam';
360 foreach my $opt (keys %{$param}) {
361 next if $opt !~ m/^usb\d+$/;
363 if ($param->{$opt} =~ m/spice/) {
364 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
366 die "only root can set '$opt' config for real devices\n";
373 my $check_vm_modify_config_perm = sub {
374 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
376 return 1 if $authuser eq 'root@pam';
378 foreach my $opt (@$key_list) {
379 # some checks (e.g., disk, serial port, usb) need to be done somewhere
380 # else, as there the permission can be value dependend
381 next if PVE
::QemuServer
::is_valid_drivename
($opt);
382 next if $opt eq 'cdrom';
383 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
386 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
387 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
388 } elsif ($memoryoptions->{$opt}) {
389 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
390 } elsif ($hwtypeoptions->{$opt}) {
391 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
392 } elsif ($generaloptions->{$opt}) {
393 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
394 # special case for startup since it changes host behaviour
395 if ($opt eq 'startup') {
396 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
398 } elsif ($vmpoweroptions->{$opt}) {
399 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
400 } elsif ($diskoptions->{$opt}) {
401 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
402 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
403 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
404 } elsif ($cloudinitoptions->{$opt}) {
405 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
406 } elsif ($opt eq 'vmstate') {
407 # the user needs Disk and PowerMgmt privileges to change the vmstate
408 # also needs privileges on the storage, that will be checked later
409 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
411 # catches hostpci\d+, args, lock, etc.
412 # new options will be checked here
413 die "only root can set '$opt' config\n";
420 __PACKAGE__-
>register_method({
424 description
=> "Virtual machine index (per node).",
426 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
430 protected
=> 1, # qemu pid files are only readable by root
432 additionalProperties
=> 0,
434 node
=> get_standard_option
('pve-node'),
438 description
=> "Determine the full status of active VMs.",
446 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
448 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
453 my $rpcenv = PVE
::RPCEnvironment
::get
();
454 my $authuser = $rpcenv->get_user();
456 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
459 foreach my $vmid (keys %$vmstatus) {
460 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
462 my $data = $vmstatus->{$vmid};
469 my $parse_restore_archive = sub {
470 my ($storecfg, $archive) = @_;
472 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
474 if (defined($archive_storeid)) {
475 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
476 if ($scfg->{type
} eq 'pbs') {
483 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
491 __PACKAGE__-
>register_method({
495 description
=> "Create or restore a virtual machine.",
497 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
498 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
499 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
500 user
=> 'all', # check inside
505 additionalProperties
=> 0,
506 properties
=> PVE
::QemuServer
::json_config_properties
(
508 node
=> get_standard_option
('pve-node'),
509 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
511 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.",
515 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
517 storage
=> get_standard_option
('pve-storage-id', {
518 description
=> "Default storage.",
520 completion
=> \
&PVE
::QemuServer
::complete_storage
,
525 description
=> "Allow to overwrite existing VM.",
526 requires
=> 'archive',
531 description
=> "Assign a unique random ethernet address.",
532 requires
=> 'archive',
537 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
538 requires
=> 'archive',
542 type
=> 'string', format
=> 'pve-poolid',
543 description
=> "Add the VM to the specified pool.",
546 description
=> "Override I/O bandwidth limit (in KiB/s).",
550 default => 'restore limit from datacenter or storage config',
556 description
=> "Start VM after it was created successfully.",
566 my $rpcenv = PVE
::RPCEnvironment
::get
();
567 my $authuser = $rpcenv->get_user();
569 my $node = extract_param
($param, 'node');
570 my $vmid = extract_param
($param, 'vmid');
572 my $archive = extract_param
($param, 'archive');
573 my $is_restore = !!$archive;
575 my $bwlimit = extract_param
($param, 'bwlimit');
576 my $force = extract_param
($param, 'force');
577 my $pool = extract_param
($param, 'pool');
578 my $start_after_create = extract_param
($param, 'start');
579 my $storage = extract_param
($param, 'storage');
580 my $unique = extract_param
($param, 'unique');
581 my $live_restore = extract_param
($param, 'live-restore');
583 if (defined(my $ssh_keys = $param->{sshkeys
})) {
584 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
585 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
588 PVE
::Cluster
::check_cfs_quorum
();
590 my $filename = PVE
::QemuConfig-
>config_file($vmid);
591 my $storecfg = PVE
::Storage
::config
();
593 if (defined($pool)) {
594 $rpcenv->check_pool_exist($pool);
597 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
598 if defined($storage);
600 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
602 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
604 } elsif ($archive && $force && (-f
$filename) &&
605 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
606 # OK: user has VM.Backup permissions, and want to restore an existing VM
612 &$resolve_cdrom_alias($param);
614 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
616 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
618 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
619 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
621 &$check_cpu_model_access($rpcenv, $authuser, $param);
623 foreach my $opt (keys %$param) {
624 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
625 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
626 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
628 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
629 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
633 PVE
::QemuServer
::add_random_macs
($param);
635 my $keystr = join(' ', keys %$param);
636 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
638 if ($archive eq '-') {
639 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
640 $archive = { type
=> 'pipe' };
642 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
644 $archive = $parse_restore_archive->($storecfg, $archive);
648 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
650 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
651 die "$emsg $@" if $@;
653 my $restored_data = 0;
654 my $restorefn = sub {
655 my $conf = PVE
::QemuConfig-
>load_config($vmid);
657 PVE
::QemuConfig-
>check_protection($conf, $emsg);
659 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
662 my $restore_options = {
667 live
=> $live_restore,
669 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
670 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
672 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
673 } elsif ($archive->{type
} eq 'pbs') {
674 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
676 die "unknown backup archive type\n";
680 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
681 # Convert restored VM to template if backup was VM template
682 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
683 warn "Convert to template.\n";
684 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
689 # ensure no old replication state are exists
690 PVE
::ReplicationState
::delete_guest_states
($vmid);
692 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
694 if ($start_after_create && !$live_restore) {
695 print "Execute autostart\n";
696 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
702 # ensure no old replication state are exists
703 PVE
::ReplicationState
::delete_guest_states
($vmid);
707 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
711 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
713 if (!$conf->{boot
}) {
714 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
715 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
718 # auto generate uuid if user did not specify smbios1 option
719 if (!$conf->{smbios1
}) {
720 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
723 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
724 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
727 my $machine = $conf->{machine
};
728 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
729 # always pin Windows' machine version on create, they get to easily confused
730 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
731 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
735 PVE
::QemuConfig-
>write_config($vmid, $conf);
741 foreach my $volid (@$vollist) {
742 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
748 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
751 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
753 if ($start_after_create) {
754 print "Execute autostart\n";
755 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
760 my ($code, $worker_name);
762 $worker_name = 'qmrestore';
764 eval { $restorefn->() };
766 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
768 if ($restored_data) {
769 warn "error after data was restored, VM disks should be OK but config may "
770 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
772 warn "error before or during data restore, some or all disks were not "
773 ."completely restored. VM $vmid state is NOT cleaned up.\n";
779 $worker_name = 'qmcreate';
781 eval { $createfn->() };
784 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
785 unlink($conffile) or die "failed to remove config file: $!\n";
793 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
796 __PACKAGE__-
>register_method({
801 description
=> "Directory index",
806 additionalProperties
=> 0,
808 node
=> get_standard_option
('pve-node'),
809 vmid
=> get_standard_option
('pve-vmid'),
817 subdir
=> { type
=> 'string' },
820 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
826 { subdir
=> 'config' },
827 { subdir
=> 'pending' },
828 { subdir
=> 'status' },
829 { subdir
=> 'unlink' },
830 { subdir
=> 'vncproxy' },
831 { subdir
=> 'termproxy' },
832 { subdir
=> 'migrate' },
833 { subdir
=> 'resize' },
834 { subdir
=> 'move' },
836 { subdir
=> 'rrddata' },
837 { subdir
=> 'monitor' },
838 { subdir
=> 'agent' },
839 { subdir
=> 'snapshot' },
840 { subdir
=> 'spiceproxy' },
841 { subdir
=> 'sendkey' },
842 { subdir
=> 'firewall' },
848 __PACKAGE__-
>register_method ({
849 subclass
=> "PVE::API2::Firewall::VM",
850 path
=> '{vmid}/firewall',
853 __PACKAGE__-
>register_method ({
854 subclass
=> "PVE::API2::Qemu::Agent",
855 path
=> '{vmid}/agent',
858 __PACKAGE__-
>register_method({
860 path
=> '{vmid}/rrd',
862 protected
=> 1, # fixme: can we avoid that?
864 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
866 description
=> "Read VM RRD statistics (returns PNG)",
868 additionalProperties
=> 0,
870 node
=> get_standard_option
('pve-node'),
871 vmid
=> get_standard_option
('pve-vmid'),
873 description
=> "Specify the time frame you are interested in.",
875 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
878 description
=> "The list of datasources you want to display.",
879 type
=> 'string', format
=> 'pve-configid-list',
882 description
=> "The RRD consolidation function",
884 enum
=> [ 'AVERAGE', 'MAX' ],
892 filename
=> { type
=> 'string' },
898 return PVE
::RRD
::create_rrd_graph
(
899 "pve2-vm/$param->{vmid}", $param->{timeframe
},
900 $param->{ds
}, $param->{cf
});
904 __PACKAGE__-
>register_method({
906 path
=> '{vmid}/rrddata',
908 protected
=> 1, # fixme: can we avoid that?
910 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
912 description
=> "Read VM RRD statistics",
914 additionalProperties
=> 0,
916 node
=> get_standard_option
('pve-node'),
917 vmid
=> get_standard_option
('pve-vmid'),
919 description
=> "Specify the time frame you are interested in.",
921 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
924 description
=> "The RRD consolidation function",
926 enum
=> [ 'AVERAGE', 'MAX' ],
941 return PVE
::RRD
::create_rrd_data
(
942 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
946 __PACKAGE__-
>register_method({
948 path
=> '{vmid}/config',
951 description
=> "Get the virtual machine configuration with pending configuration " .
952 "changes applied. Set the 'current' parameter to get the current configuration instead.",
954 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
957 additionalProperties
=> 0,
959 node
=> get_standard_option
('pve-node'),
960 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
962 description
=> "Get current values (instead of pending values).",
967 snapshot
=> get_standard_option
('pve-snapshot-name', {
968 description
=> "Fetch config values from given snapshot.",
971 my ($cmd, $pname, $cur, $args) = @_;
972 PVE
::QemuConfig-
>snapshot_list($args->[0]);
978 description
=> "The VM configuration.",
980 properties
=> PVE
::QemuServer
::json_config_properties
({
983 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
990 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
991 current
=> "cannot use 'snapshot' parameter with 'current'"})
992 if ($param->{snapshot
} && $param->{current
});
995 if ($param->{snapshot
}) {
996 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
998 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1000 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1005 __PACKAGE__-
>register_method({
1006 name
=> 'vm_pending',
1007 path
=> '{vmid}/pending',
1010 description
=> "Get the virtual machine configuration with both current and pending values.",
1012 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1015 additionalProperties
=> 0,
1017 node
=> get_standard_option
('pve-node'),
1018 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1027 description
=> "Configuration option name.",
1031 description
=> "Current value.",
1036 description
=> "Pending value.",
1041 description
=> "Indicates a pending delete request if present and not 0. " .
1042 "The value 2 indicates a force-delete request.",
1054 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1056 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1058 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1059 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1061 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1064 # POST/PUT {vmid}/config implementation
1066 # The original API used PUT (idempotent) an we assumed that all operations
1067 # are fast. But it turned out that almost any configuration change can
1068 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1069 # time to complete and have side effects (not idempotent).
1071 # The new implementation uses POST and forks a worker process. We added
1072 # a new option 'background_delay'. If specified we wait up to
1073 # 'background_delay' second for the worker task to complete. It returns null
1074 # if the task is finished within that time, else we return the UPID.
1076 my $update_vm_api = sub {
1077 my ($param, $sync) = @_;
1079 my $rpcenv = PVE
::RPCEnvironment
::get
();
1081 my $authuser = $rpcenv->get_user();
1083 my $node = extract_param
($param, 'node');
1085 my $vmid = extract_param
($param, 'vmid');
1087 my $digest = extract_param
($param, 'digest');
1089 my $background_delay = extract_param
($param, 'background_delay');
1091 if (defined(my $cipassword = $param->{cipassword
})) {
1092 # Same logic as in cloud-init (but with the regex fixed...)
1093 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1094 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1097 my @paramarr = (); # used for log message
1098 foreach my $key (sort keys %$param) {
1099 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1100 push @paramarr, "-$key", $value;
1103 my $skiplock = extract_param
($param, 'skiplock');
1104 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1105 if $skiplock && $authuser ne 'root@pam';
1107 my $delete_str = extract_param
($param, 'delete');
1109 my $revert_str = extract_param
($param, 'revert');
1111 my $force = extract_param
($param, 'force');
1113 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1114 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1115 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1118 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1120 my $storecfg = PVE
::Storage
::config
();
1122 my $defaults = PVE
::QemuServer
::load_defaults
();
1124 &$resolve_cdrom_alias($param);
1126 # now try to verify all parameters
1129 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1130 if (!PVE
::QemuServer
::option_exists
($opt)) {
1131 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1134 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1135 "-revert $opt' at the same time" })
1136 if defined($param->{$opt});
1138 $revert->{$opt} = 1;
1142 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1143 $opt = 'ide2' if $opt eq 'cdrom';
1145 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1146 "-delete $opt' at the same time" })
1147 if defined($param->{$opt});
1149 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1150 "-revert $opt' at the same time" })
1153 if (!PVE
::QemuServer
::option_exists
($opt)) {
1154 raise_param_exc
({ delete => "unknown option '$opt'" });
1160 my $repl_conf = PVE
::ReplicationConfig-
>new();
1161 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1162 my $check_replication = sub {
1164 return if !$is_replicated;
1165 my $volid = $drive->{file
};
1166 return if !$volid || !($drive->{replicate
}//1);
1167 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1169 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1170 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1171 if !defined($storeid);
1173 return if defined($volname) && $volname eq 'cloudinit';
1176 if ($volid =~ $NEW_DISK_RE) {
1178 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1180 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1182 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1183 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1184 return if $scfg->{shared
};
1185 die "cannot add non-replicatable volume to a replicated VM\n";
1188 foreach my $opt (keys %$param) {
1189 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1190 # cleanup drive path
1191 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1192 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1193 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1194 $check_replication->($drive);
1195 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1196 } elsif ($opt =~ m/^net(\d+)$/) {
1198 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1199 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1200 } elsif ($opt eq 'vmgenid') {
1201 if ($param->{$opt} eq '1') {
1202 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1204 } elsif ($opt eq 'hookscript') {
1205 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1206 raise_param_exc
({ $opt => $@ }) if $@;
1210 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1212 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1214 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1216 my $updatefn = sub {
1218 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1220 die "checksum missmatch (file change by other user?)\n"
1221 if $digest && $digest ne $conf->{digest
};
1223 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1225 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1226 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1227 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1228 delete $conf->{lock}; # for check lock check, not written out
1229 push @delete, 'lock'; # this is the real deal to write it out
1231 push @delete, 'runningmachine' if $conf->{runningmachine
};
1232 push @delete, 'runningcpu' if $conf->{runningcpu
};
1235 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1237 foreach my $opt (keys %$revert) {
1238 if (defined($conf->{$opt})) {
1239 $param->{$opt} = $conf->{$opt};
1240 } elsif (defined($conf->{pending
}->{$opt})) {
1245 if ($param->{memory
} || defined($param->{balloon
})) {
1246 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1247 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1249 die "balloon value too large (must be smaller than assigned memory)\n"
1250 if $balloon && $balloon > $maxmem;
1253 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1257 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1259 # write updates to pending section
1261 my $modified = {}; # record what $option we modify
1264 if (my $boot = $conf->{boot
}) {
1265 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1266 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1268 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1270 my $check_drive_perms = sub {
1271 my ($opt, $val) = @_;
1272 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1273 # FIXME: cloudinit: CDROM or Disk?
1274 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1275 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1277 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1281 foreach my $opt (@delete) {
1282 $modified->{$opt} = 1;
1283 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1285 # value of what we want to delete, independent if pending or not
1286 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1287 if (!defined($val)) {
1288 warn "cannot delete '$opt' - not set in current configuration!\n";
1289 $modified->{$opt} = 0;
1292 my $is_pending_val = defined($conf->{pending
}->{$opt});
1293 delete $conf->{pending
}->{$opt};
1295 # remove from bootorder if necessary
1296 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1297 @bootorder = grep {$_ ne $opt} @bootorder;
1298 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1299 $modified->{boot
} = 1;
1302 if ($opt =~ m/^unused/) {
1303 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1304 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1305 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1306 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1307 delete $conf->{$opt};
1308 PVE
::QemuConfig-
>write_config($vmid, $conf);
1310 } elsif ($opt eq 'vmstate') {
1311 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1312 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1313 delete $conf->{$opt};
1314 PVE
::QemuConfig-
>write_config($vmid, $conf);
1316 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1317 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1318 $check_drive_perms->($opt, $val);
1319 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1321 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1322 PVE
::QemuConfig-
>write_config($vmid, $conf);
1323 } elsif ($opt =~ m/^serial\d+$/) {
1324 if ($val eq 'socket') {
1325 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1326 } elsif ($authuser ne 'root@pam') {
1327 die "only root can delete '$opt' config for real devices\n";
1329 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1330 PVE
::QemuConfig-
>write_config($vmid, $conf);
1331 } elsif ($opt =~ m/^usb\d+$/) {
1332 if ($val =~ m/spice/) {
1333 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1334 } elsif ($authuser ne 'root@pam') {
1335 die "only root can delete '$opt' config for real devices\n";
1337 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1338 PVE
::QemuConfig-
>write_config($vmid, $conf);
1340 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1341 PVE
::QemuConfig-
>write_config($vmid, $conf);
1345 foreach my $opt (keys %$param) { # add/change
1346 $modified->{$opt} = 1;
1347 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1348 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1350 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1352 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1354 if ($conf->{$opt}) {
1355 $check_drive_perms->($opt, $conf->{$opt});
1359 $check_drive_perms->($opt, $param->{$opt});
1360 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1361 if defined($conf->{pending
}->{$opt});
1363 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1365 # default legacy boot order implies all cdroms anyway
1367 # append new CD drives to bootorder to mark them bootable
1368 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1369 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1370 push @bootorder, $opt;
1371 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1372 $modified->{boot
} = 1;
1375 } elsif ($opt =~ m/^serial\d+/) {
1376 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1377 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1378 } elsif ($authuser ne 'root@pam') {
1379 die "only root can modify '$opt' config for real devices\n";
1381 $conf->{pending
}->{$opt} = $param->{$opt};
1382 } elsif ($opt =~ m/^usb\d+/) {
1383 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1384 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1385 } elsif ($authuser ne 'root@pam') {
1386 die "only root can modify '$opt' config for real devices\n";
1388 $conf->{pending
}->{$opt} = $param->{$opt};
1390 $conf->{pending
}->{$opt} = $param->{$opt};
1392 if ($opt eq 'boot') {
1393 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1394 if ($new_bootcfg->{order
}) {
1395 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1396 for my $dev (@devs) {
1397 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1398 my $deleted = grep {$_ eq $dev} @delete;
1399 die "invalid bootorder: device '$dev' does not exist'\n"
1400 if !$exists || $deleted;
1403 # remove legacy boot order settings if new one set
1404 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1405 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1406 if $conf->{bootdisk
};
1410 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1411 PVE
::QemuConfig-
>write_config($vmid, $conf);
1414 # remove pending changes when nothing changed
1415 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1416 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1417 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1419 return if !scalar(keys %{$conf->{pending
}});
1421 my $running = PVE
::QemuServer
::check_running
($vmid);
1423 # apply pending changes
1425 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1429 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1431 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1433 raise_param_exc
($errors) if scalar(keys %$errors);
1442 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1444 if ($background_delay) {
1446 # Note: It would be better to do that in the Event based HTTPServer
1447 # to avoid blocking call to sleep.
1449 my $end_time = time() + $background_delay;
1451 my $task = PVE
::Tools
::upid_decode
($upid);
1454 while (time() < $end_time) {
1455 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1457 sleep(1); # this gets interrupted when child process ends
1461 my $status = PVE
::Tools
::upid_read_status
($upid);
1462 return if !PVE
::Tools
::upid_status_is_error
($status);
1471 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1474 my $vm_config_perm_list = [
1479 'VM.Config.Network',
1481 'VM.Config.Options',
1482 'VM.Config.Cloudinit',
1485 __PACKAGE__-
>register_method({
1486 name
=> 'update_vm_async',
1487 path
=> '{vmid}/config',
1491 description
=> "Set virtual machine options (asynchrounous API).",
1493 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1496 additionalProperties
=> 0,
1497 properties
=> PVE
::QemuServer
::json_config_properties
(
1499 node
=> get_standard_option
('pve-node'),
1500 vmid
=> get_standard_option
('pve-vmid'),
1501 skiplock
=> get_standard_option
('skiplock'),
1503 type
=> 'string', format
=> 'pve-configid-list',
1504 description
=> "A list of settings you want to delete.",
1508 type
=> 'string', format
=> 'pve-configid-list',
1509 description
=> "Revert a pending change.",
1514 description
=> $opt_force_description,
1516 requires
=> 'delete',
1520 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1524 background_delay
=> {
1526 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1537 code
=> $update_vm_api,
1540 __PACKAGE__-
>register_method({
1541 name
=> 'update_vm',
1542 path
=> '{vmid}/config',
1546 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1548 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1551 additionalProperties
=> 0,
1552 properties
=> PVE
::QemuServer
::json_config_properties
(
1554 node
=> get_standard_option
('pve-node'),
1555 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1556 skiplock
=> get_standard_option
('skiplock'),
1558 type
=> 'string', format
=> 'pve-configid-list',
1559 description
=> "A list of settings you want to delete.",
1563 type
=> 'string', format
=> 'pve-configid-list',
1564 description
=> "Revert a pending change.",
1569 description
=> $opt_force_description,
1571 requires
=> 'delete',
1575 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1581 returns
=> { type
=> 'null' },
1584 &$update_vm_api($param, 1);
1589 __PACKAGE__-
>register_method({
1590 name
=> 'destroy_vm',
1595 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1596 ." and firewall rules",
1598 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1601 additionalProperties
=> 0,
1603 node
=> get_standard_option
('pve-node'),
1604 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1605 skiplock
=> get_standard_option
('skiplock'),
1608 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1611 'destroy-unreferenced-disks' => {
1613 description
=> "If set, destroy additionally all disks not referenced in the config"
1614 ." but with a matching VMID from all enabled storages.",
1626 my $rpcenv = PVE
::RPCEnvironment
::get
();
1627 my $authuser = $rpcenv->get_user();
1628 my $vmid = $param->{vmid
};
1630 my $skiplock = $param->{skiplock
};
1631 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1632 if $skiplock && $authuser ne 'root@pam';
1634 my $early_checks = sub {
1636 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1637 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1639 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1641 if (!$param->{purge
}) {
1642 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1644 # don't allow destroy if with replication jobs but no purge param
1645 my $repl_conf = PVE
::ReplicationConfig-
>new();
1646 $repl_conf->check_for_existing_jobs($vmid);
1649 die "VM $vmid is running - destroy failed\n"
1650 if PVE
::QemuServer
::check_running
($vmid);
1660 my $storecfg = PVE
::Storage
::config
();
1662 syslog
('info', "destroy VM $vmid: $upid\n");
1663 PVE
::QemuConfig-
>lock_config($vmid, sub {
1664 # repeat, config might have changed
1665 my $ha_managed = $early_checks->();
1667 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1669 PVE
::QemuServer
::destroy_vm
(
1672 $skiplock, { lock => 'destroyed' },
1673 $purge_unreferenced,
1676 PVE
::AccessControl
::remove_vm_access
($vmid);
1677 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1678 if ($param->{purge
}) {
1679 print "purging VM $vmid from related configurations..\n";
1680 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1681 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1684 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1685 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1689 # only now remove the zombie config, else we can have reuse race
1690 PVE
::QemuConfig-
>destroy_config($vmid);
1694 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1697 __PACKAGE__-
>register_method({
1699 path
=> '{vmid}/unlink',
1703 description
=> "Unlink/delete disk images.",
1705 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1708 additionalProperties
=> 0,
1710 node
=> get_standard_option
('pve-node'),
1711 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1713 type
=> 'string', format
=> 'pve-configid-list',
1714 description
=> "A list of disk IDs you want to delete.",
1718 description
=> $opt_force_description,
1723 returns
=> { type
=> 'null'},
1727 $param->{delete} = extract_param
($param, 'idlist');
1729 __PACKAGE__-
>update_vm($param);
1734 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1735 my $gen_rand_chars = sub {
1738 die "invalid length $length" if $length < 1;
1740 my $min = ord('!'); # first printable ascii
1742 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1743 die "failed to generate random bytes!\n"
1746 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1753 __PACKAGE__-
>register_method({
1755 path
=> '{vmid}/vncproxy',
1759 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1761 description
=> "Creates a TCP VNC proxy connections.",
1763 additionalProperties
=> 0,
1765 node
=> get_standard_option
('pve-node'),
1766 vmid
=> get_standard_option
('pve-vmid'),
1770 description
=> "starts websockify instead of vncproxy",
1772 'generate-password' => {
1776 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1781 additionalProperties
=> 0,
1783 user
=> { type
=> 'string' },
1784 ticket
=> { type
=> 'string' },
1787 description
=> "Returned if requested with 'generate-password' param."
1788 ." Consists of printable ASCII characters ('!' .. '~').",
1791 cert
=> { type
=> 'string' },
1792 port
=> { type
=> 'integer' },
1793 upid
=> { type
=> 'string' },
1799 my $rpcenv = PVE
::RPCEnvironment
::get
();
1801 my $authuser = $rpcenv->get_user();
1803 my $vmid = $param->{vmid
};
1804 my $node = $param->{node
};
1805 my $websocket = $param->{websocket
};
1807 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1811 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1812 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1815 my $authpath = "/vms/$vmid";
1817 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1818 my $password = $ticket;
1819 if ($param->{'generate-password'}) {
1820 $password = $gen_rand_chars->(8);
1823 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1829 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1830 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1831 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1832 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1833 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1835 $family = PVE
::Tools
::get_host_address_family
($node);
1838 my $port = PVE
::Tools
::next_vnc_port
($family);
1845 syslog
('info', "starting vnc proxy $upid\n");
1849 if (defined($serial)) {
1851 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1853 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1854 '-timeout', $timeout, '-authpath', $authpath,
1855 '-perm', 'Sys.Console'];
1857 if ($param->{websocket
}) {
1858 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1859 push @$cmd, '-notls', '-listen', 'localhost';
1862 push @$cmd, '-c', @$remcmd, @$termcmd;
1864 PVE
::Tools
::run_command
($cmd);
1868 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1870 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1872 my $sock = IO
::Socket
::IP-
>new(
1877 GetAddrInfoFlags
=> 0,
1878 ) or die "failed to create socket: $!\n";
1879 # Inside the worker we shouldn't have any previous alarms
1880 # running anyway...:
1882 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1884 accept(my $cli, $sock) or die "connection failed: $!\n";
1887 if (PVE
::Tools
::run_command
($cmd,
1888 output
=> '>&'.fileno($cli),
1889 input
=> '<&'.fileno($cli),
1892 die "Failed to run vncproxy.\n";
1899 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1901 PVE
::Tools
::wait_for_vnc_port
($port);
1910 $res->{password
} = $password if $param->{'generate-password'};
1915 __PACKAGE__-
>register_method({
1916 name
=> 'termproxy',
1917 path
=> '{vmid}/termproxy',
1921 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1923 description
=> "Creates a TCP proxy connections.",
1925 additionalProperties
=> 0,
1927 node
=> get_standard_option
('pve-node'),
1928 vmid
=> get_standard_option
('pve-vmid'),
1932 enum
=> [qw(serial0 serial1 serial2 serial3)],
1933 description
=> "opens a serial terminal (defaults to display)",
1938 additionalProperties
=> 0,
1940 user
=> { type
=> 'string' },
1941 ticket
=> { type
=> 'string' },
1942 port
=> { type
=> 'integer' },
1943 upid
=> { type
=> 'string' },
1949 my $rpcenv = PVE
::RPCEnvironment
::get
();
1951 my $authuser = $rpcenv->get_user();
1953 my $vmid = $param->{vmid
};
1954 my $node = $param->{node
};
1955 my $serial = $param->{serial
};
1957 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1959 if (!defined($serial)) {
1961 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1962 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1966 my $authpath = "/vms/$vmid";
1968 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1973 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1974 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1975 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1976 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1977 push @$remcmd, '--';
1979 $family = PVE
::Tools
::get_host_address_family
($node);
1982 my $port = PVE
::Tools
::next_vnc_port
($family);
1984 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1985 push @$termcmd, '-iface', $serial if $serial;
1990 syslog
('info', "starting qemu termproxy $upid\n");
1992 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1993 '--perm', 'VM.Console', '--'];
1994 push @$cmd, @$remcmd, @$termcmd;
1996 PVE
::Tools
::run_command
($cmd);
1999 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2001 PVE
::Tools
::wait_for_vnc_port
($port);
2011 __PACKAGE__-
>register_method({
2012 name
=> 'vncwebsocket',
2013 path
=> '{vmid}/vncwebsocket',
2016 description
=> "You also need to pass a valid ticket (vncticket).",
2017 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2019 description
=> "Opens a weksocket for VNC traffic.",
2021 additionalProperties
=> 0,
2023 node
=> get_standard_option
('pve-node'),
2024 vmid
=> get_standard_option
('pve-vmid'),
2026 description
=> "Ticket from previous call to vncproxy.",
2031 description
=> "Port number returned by previous vncproxy call.",
2041 port
=> { type
=> 'string' },
2047 my $rpcenv = PVE
::RPCEnvironment
::get
();
2049 my $authuser = $rpcenv->get_user();
2051 my $vmid = $param->{vmid
};
2052 my $node = $param->{node
};
2054 my $authpath = "/vms/$vmid";
2056 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2058 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2060 # Note: VNC ports are acessible from outside, so we do not gain any
2061 # security if we verify that $param->{port} belongs to VM $vmid. This
2062 # check is done by verifying the VNC ticket (inside VNC protocol).
2064 my $port = $param->{port
};
2066 return { port
=> $port };
2069 __PACKAGE__-
>register_method({
2070 name
=> 'spiceproxy',
2071 path
=> '{vmid}/spiceproxy',
2076 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2078 description
=> "Returns a SPICE configuration to connect to the VM.",
2080 additionalProperties
=> 0,
2082 node
=> get_standard_option
('pve-node'),
2083 vmid
=> get_standard_option
('pve-vmid'),
2084 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2087 returns
=> get_standard_option
('remote-viewer-config'),
2091 my $rpcenv = PVE
::RPCEnvironment
::get
();
2093 my $authuser = $rpcenv->get_user();
2095 my $vmid = $param->{vmid
};
2096 my $node = $param->{node
};
2097 my $proxy = $param->{proxy
};
2099 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2100 my $title = "VM $vmid";
2101 $title .= " - ". $conf->{name
} if $conf->{name
};
2103 my $port = PVE
::QemuServer
::spice_port
($vmid);
2105 my ($ticket, undef, $remote_viewer_config) =
2106 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2108 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2109 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2111 return $remote_viewer_config;
2114 __PACKAGE__-
>register_method({
2116 path
=> '{vmid}/status',
2119 description
=> "Directory index",
2124 additionalProperties
=> 0,
2126 node
=> get_standard_option
('pve-node'),
2127 vmid
=> get_standard_option
('pve-vmid'),
2135 subdir
=> { type
=> 'string' },
2138 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2144 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2147 { subdir
=> 'current' },
2148 { subdir
=> 'start' },
2149 { subdir
=> 'stop' },
2150 { subdir
=> 'reset' },
2151 { subdir
=> 'shutdown' },
2152 { subdir
=> 'suspend' },
2153 { subdir
=> 'reboot' },
2159 __PACKAGE__-
>register_method({
2160 name
=> 'vm_status',
2161 path
=> '{vmid}/status/current',
2164 protected
=> 1, # qemu pid files are only readable by root
2165 description
=> "Get virtual machine status.",
2167 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2170 additionalProperties
=> 0,
2172 node
=> get_standard_option
('pve-node'),
2173 vmid
=> get_standard_option
('pve-vmid'),
2179 %$PVE::QemuServer
::vmstatus_return_properties
,
2181 description
=> "HA manager service status.",
2185 description
=> "Qemu VGA configuration supports spice.",
2190 description
=> "Qemu GuestAgent enabled in config.",
2200 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2202 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2203 my $status = $vmstatus->{$param->{vmid
}};
2205 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2207 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2208 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2213 __PACKAGE__-
>register_method({
2215 path
=> '{vmid}/status/start',
2219 description
=> "Start virtual machine.",
2221 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2224 additionalProperties
=> 0,
2226 node
=> get_standard_option
('pve-node'),
2227 vmid
=> get_standard_option
('pve-vmid',
2228 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2229 skiplock
=> get_standard_option
('skiplock'),
2230 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2231 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2234 enum
=> ['secure', 'insecure'],
2235 description
=> "Migration traffic is encrypted using an SSH " .
2236 "tunnel by default. On secure, completely private networks " .
2237 "this can be disabled to increase performance.",
2240 migration_network
=> {
2241 type
=> 'string', format
=> 'CIDR',
2242 description
=> "CIDR of the (sub) network that is used for migration.",
2245 machine
=> get_standard_option
('pve-qemu-machine'),
2247 description
=> "Override QEMU's -cpu argument with the given string.",
2251 targetstorage
=> get_standard_option
('pve-targetstorage'),
2253 description
=> "Wait maximal timeout seconds.",
2256 default => 'max(30, vm memory in GiB)',
2267 my $rpcenv = PVE
::RPCEnvironment
::get
();
2268 my $authuser = $rpcenv->get_user();
2270 my $node = extract_param
($param, 'node');
2271 my $vmid = extract_param
($param, 'vmid');
2272 my $timeout = extract_param
($param, 'timeout');
2274 my $machine = extract_param
($param, 'machine');
2275 my $force_cpu = extract_param
($param, 'force-cpu');
2277 my $get_root_param = sub {
2278 my $value = extract_param
($param, $_[0]);
2279 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2280 if $value && $authuser ne 'root@pam';
2284 my $stateuri = $get_root_param->('stateuri');
2285 my $skiplock = $get_root_param->('skiplock');
2286 my $migratedfrom = $get_root_param->('migratedfrom');
2287 my $migration_type = $get_root_param->('migration_type');
2288 my $migration_network = $get_root_param->('migration_network');
2289 my $targetstorage = $get_root_param->('targetstorage');
2293 if ($targetstorage) {
2294 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2296 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2297 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2301 # read spice ticket from STDIN
2303 my $nbd_protocol_version = 0;
2304 my $replicated_volumes = {};
2305 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2306 while (defined(my $line = <STDIN
>)) {
2308 if ($line =~ m/^spice_ticket: (.+)$/) {
2310 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2311 $nbd_protocol_version = $1;
2312 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2313 $replicated_volumes->{$1} = 1;
2315 # fallback for old source node
2316 $spice_ticket = $line;
2321 PVE
::Cluster
::check_cfs_quorum
();
2323 my $storecfg = PVE
::Storage
::config
();
2325 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2329 print "Requesting HA start for VM $vmid\n";
2331 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2332 PVE
::Tools
::run_command
($cmd);
2336 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2343 syslog
('info', "start VM $vmid: $upid\n");
2345 my $migrate_opts = {
2346 migratedfrom
=> $migratedfrom,
2347 spice_ticket
=> $spice_ticket,
2348 network
=> $migration_network,
2349 type
=> $migration_type,
2350 storagemap
=> $storagemap,
2351 nbd_proto_version
=> $nbd_protocol_version,
2352 replicated_volumes
=> $replicated_volumes,
2356 statefile
=> $stateuri,
2357 skiplock
=> $skiplock,
2358 forcemachine
=> $machine,
2359 timeout
=> $timeout,
2360 forcecpu
=> $force_cpu,
2363 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2367 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2371 __PACKAGE__-
>register_method({
2373 path
=> '{vmid}/status/stop',
2377 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2378 "is akin to pulling the power plug of a running computer and may damage the VM data",
2380 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2383 additionalProperties
=> 0,
2385 node
=> get_standard_option
('pve-node'),
2386 vmid
=> get_standard_option
('pve-vmid',
2387 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2388 skiplock
=> get_standard_option
('skiplock'),
2389 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2391 description
=> "Wait maximal timeout seconds.",
2397 description
=> "Do not deactivate storage volumes.",
2410 my $rpcenv = PVE
::RPCEnvironment
::get
();
2411 my $authuser = $rpcenv->get_user();
2413 my $node = extract_param
($param, 'node');
2414 my $vmid = extract_param
($param, 'vmid');
2416 my $skiplock = extract_param
($param, 'skiplock');
2417 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2418 if $skiplock && $authuser ne 'root@pam';
2420 my $keepActive = extract_param
($param, 'keepActive');
2421 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2422 if $keepActive && $authuser ne 'root@pam';
2424 my $migratedfrom = extract_param
($param, 'migratedfrom');
2425 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2426 if $migratedfrom && $authuser ne 'root@pam';
2429 my $storecfg = PVE
::Storage
::config
();
2431 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2436 print "Requesting HA stop for VM $vmid\n";
2438 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2439 PVE
::Tools
::run_command
($cmd);
2443 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2449 syslog
('info', "stop VM $vmid: $upid\n");
2451 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2452 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2456 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2460 __PACKAGE__-
>register_method({
2462 path
=> '{vmid}/status/reset',
2466 description
=> "Reset virtual machine.",
2468 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2471 additionalProperties
=> 0,
2473 node
=> get_standard_option
('pve-node'),
2474 vmid
=> get_standard_option
('pve-vmid',
2475 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2476 skiplock
=> get_standard_option
('skiplock'),
2485 my $rpcenv = PVE
::RPCEnvironment
::get
();
2487 my $authuser = $rpcenv->get_user();
2489 my $node = extract_param
($param, 'node');
2491 my $vmid = extract_param
($param, 'vmid');
2493 my $skiplock = extract_param
($param, 'skiplock');
2494 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2495 if $skiplock && $authuser ne 'root@pam';
2497 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2502 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2507 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2510 __PACKAGE__-
>register_method({
2511 name
=> 'vm_shutdown',
2512 path
=> '{vmid}/status/shutdown',
2516 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2517 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2519 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2522 additionalProperties
=> 0,
2524 node
=> get_standard_option
('pve-node'),
2525 vmid
=> get_standard_option
('pve-vmid',
2526 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2527 skiplock
=> get_standard_option
('skiplock'),
2529 description
=> "Wait maximal timeout seconds.",
2535 description
=> "Make sure the VM stops.",
2541 description
=> "Do not deactivate storage volumes.",
2554 my $rpcenv = PVE
::RPCEnvironment
::get
();
2555 my $authuser = $rpcenv->get_user();
2557 my $node = extract_param
($param, 'node');
2558 my $vmid = extract_param
($param, 'vmid');
2560 my $skiplock = extract_param
($param, 'skiplock');
2561 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2562 if $skiplock && $authuser ne 'root@pam';
2564 my $keepActive = extract_param
($param, 'keepActive');
2565 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2566 if $keepActive && $authuser ne 'root@pam';
2568 my $storecfg = PVE
::Storage
::config
();
2572 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2573 # otherwise, we will infer a shutdown command, but run into the timeout,
2574 # then when the vm is resumed, it will instantly shutdown
2576 # checking the qmp status here to get feedback to the gui/cli/api
2577 # and the status query should not take too long
2578 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2579 if ($param->{forceStop
}) {
2580 warn "VM is paused - stop instead of shutdown\n";
2583 die "VM is paused - cannot shutdown\n";
2587 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2589 my $timeout = $param->{timeout
} // 60;
2593 print "Requesting HA stop for VM $vmid\n";
2595 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2596 PVE
::Tools
::run_command
($cmd);
2600 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2607 syslog
('info', "shutdown VM $vmid: $upid\n");
2609 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2610 $shutdown, $param->{forceStop
}, $keepActive);
2614 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2618 __PACKAGE__-
>register_method({
2619 name
=> 'vm_reboot',
2620 path
=> '{vmid}/status/reboot',
2624 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2626 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2629 additionalProperties
=> 0,
2631 node
=> get_standard_option
('pve-node'),
2632 vmid
=> get_standard_option
('pve-vmid',
2633 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2635 description
=> "Wait maximal timeout seconds for the shutdown.",
2648 my $rpcenv = PVE
::RPCEnvironment
::get
();
2649 my $authuser = $rpcenv->get_user();
2651 my $node = extract_param
($param, 'node');
2652 my $vmid = extract_param
($param, 'vmid');
2654 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2656 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2661 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2662 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2666 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2669 __PACKAGE__-
>register_method({
2670 name
=> 'vm_suspend',
2671 path
=> '{vmid}/status/suspend',
2675 description
=> "Suspend virtual machine.",
2677 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2678 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2679 " on the storage for the vmstate.",
2680 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2683 additionalProperties
=> 0,
2685 node
=> get_standard_option
('pve-node'),
2686 vmid
=> get_standard_option
('pve-vmid',
2687 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2688 skiplock
=> get_standard_option
('skiplock'),
2693 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2695 statestorage
=> get_standard_option
('pve-storage-id', {
2696 description
=> "The storage for the VM state",
2697 requires
=> 'todisk',
2699 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2709 my $rpcenv = PVE
::RPCEnvironment
::get
();
2710 my $authuser = $rpcenv->get_user();
2712 my $node = extract_param
($param, 'node');
2713 my $vmid = extract_param
($param, 'vmid');
2715 my $todisk = extract_param
($param, 'todisk') // 0;
2717 my $statestorage = extract_param
($param, 'statestorage');
2719 my $skiplock = extract_param
($param, 'skiplock');
2720 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2721 if $skiplock && $authuser ne 'root@pam';
2723 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2725 die "Cannot suspend HA managed VM to disk\n"
2726 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2728 # early check for storage permission, for better user feedback
2730 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2732 if (!$statestorage) {
2733 # get statestorage from config if none is given
2734 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2735 my $storecfg = PVE
::Storage
::config
();
2736 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2739 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2745 syslog
('info', "suspend VM $vmid: $upid\n");
2747 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2752 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2753 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2756 __PACKAGE__-
>register_method({
2757 name
=> 'vm_resume',
2758 path
=> '{vmid}/status/resume',
2762 description
=> "Resume virtual machine.",
2764 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2767 additionalProperties
=> 0,
2769 node
=> get_standard_option
('pve-node'),
2770 vmid
=> get_standard_option
('pve-vmid',
2771 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2772 skiplock
=> get_standard_option
('skiplock'),
2773 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2783 my $rpcenv = PVE
::RPCEnvironment
::get
();
2785 my $authuser = $rpcenv->get_user();
2787 my $node = extract_param
($param, 'node');
2789 my $vmid = extract_param
($param, 'vmid');
2791 my $skiplock = extract_param
($param, 'skiplock');
2792 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2793 if $skiplock && $authuser ne 'root@pam';
2795 my $nocheck = extract_param
($param, 'nocheck');
2796 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2797 if $nocheck && $authuser ne 'root@pam';
2799 my $to_disk_suspended;
2801 PVE
::QemuConfig-
>lock_config($vmid, sub {
2802 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2803 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2807 die "VM $vmid not running\n"
2808 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2813 syslog
('info', "resume VM $vmid: $upid\n");
2815 if (!$to_disk_suspended) {
2816 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2818 my $storecfg = PVE
::Storage
::config
();
2819 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2825 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2828 __PACKAGE__-
>register_method({
2829 name
=> 'vm_sendkey',
2830 path
=> '{vmid}/sendkey',
2834 description
=> "Send key event to virtual machine.",
2836 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2839 additionalProperties
=> 0,
2841 node
=> get_standard_option
('pve-node'),
2842 vmid
=> get_standard_option
('pve-vmid',
2843 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2844 skiplock
=> get_standard_option
('skiplock'),
2846 description
=> "The key (qemu monitor encoding).",
2851 returns
=> { type
=> 'null'},
2855 my $rpcenv = PVE
::RPCEnvironment
::get
();
2857 my $authuser = $rpcenv->get_user();
2859 my $node = extract_param
($param, 'node');
2861 my $vmid = extract_param
($param, 'vmid');
2863 my $skiplock = extract_param
($param, 'skiplock');
2864 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2865 if $skiplock && $authuser ne 'root@pam';
2867 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2872 __PACKAGE__-
>register_method({
2873 name
=> 'vm_feature',
2874 path
=> '{vmid}/feature',
2878 description
=> "Check if feature for virtual machine is available.",
2880 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2883 additionalProperties
=> 0,
2885 node
=> get_standard_option
('pve-node'),
2886 vmid
=> get_standard_option
('pve-vmid'),
2888 description
=> "Feature to check.",
2890 enum
=> [ 'snapshot', 'clone', 'copy' ],
2892 snapname
=> get_standard_option
('pve-snapshot-name', {
2900 hasFeature
=> { type
=> 'boolean' },
2903 items
=> { type
=> 'string' },
2910 my $node = extract_param
($param, 'node');
2912 my $vmid = extract_param
($param, 'vmid');
2914 my $snapname = extract_param
($param, 'snapname');
2916 my $feature = extract_param
($param, 'feature');
2918 my $running = PVE
::QemuServer
::check_running
($vmid);
2920 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2923 my $snap = $conf->{snapshots
}->{$snapname};
2924 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2927 my $storecfg = PVE
::Storage
::config
();
2929 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2930 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2933 hasFeature
=> $hasFeature,
2934 nodes
=> [ keys %$nodelist ],
2938 __PACKAGE__-
>register_method({
2940 path
=> '{vmid}/clone',
2944 description
=> "Create a copy of virtual machine/template.",
2946 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2947 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2948 "'Datastore.AllocateSpace' on any used storage.",
2951 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2953 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2954 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2959 additionalProperties
=> 0,
2961 node
=> get_standard_option
('pve-node'),
2962 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2963 newid
=> get_standard_option
('pve-vmid', {
2964 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2965 description
=> 'VMID for the clone.' }),
2968 type
=> 'string', format
=> 'dns-name',
2969 description
=> "Set a name for the new VM.",
2974 description
=> "Description for the new VM.",
2978 type
=> 'string', format
=> 'pve-poolid',
2979 description
=> "Add the new VM to the specified pool.",
2981 snapname
=> get_standard_option
('pve-snapshot-name', {
2984 storage
=> get_standard_option
('pve-storage-id', {
2985 description
=> "Target storage for full clone.",
2989 description
=> "Target format for file storage. Only valid for full clone.",
2992 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2997 description
=> "Create a full copy of all disks. This is always done when " .
2998 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3000 target
=> get_standard_option
('pve-node', {
3001 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3005 description
=> "Override I/O bandwidth limit (in KiB/s).",
3009 default => 'clone limit from datacenter or storage config',
3019 my $rpcenv = PVE
::RPCEnvironment
::get
();
3020 my $authuser = $rpcenv->get_user();
3022 my $node = extract_param
($param, 'node');
3023 my $vmid = extract_param
($param, 'vmid');
3024 my $newid = extract_param
($param, 'newid');
3025 my $pool = extract_param
($param, 'pool');
3026 $rpcenv->check_pool_exist($pool) if defined($pool);
3028 my $snapname = extract_param
($param, 'snapname');
3029 my $storage = extract_param
($param, 'storage');
3030 my $format = extract_param
($param, 'format');
3031 my $target = extract_param
($param, 'target');
3033 my $localnode = PVE
::INotify
::nodename
();
3035 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3039 PVE
::Cluster
::check_node_exists
($target) if $target;
3041 my $storecfg = PVE
::Storage
::config
();
3044 # check if storage is enabled on local node
3045 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3047 # check if storage is available on target node
3048 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3049 # clone only works if target storage is shared
3050 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3051 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3055 PVE
::Cluster
::check_cfs_quorum
();
3057 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3060 # do all tests after lock but before forking worker - if possible
3062 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3063 PVE
::QemuConfig-
>check_lock($conf);
3065 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3066 die "unexpected state change\n" if $verify_running != $running;
3068 die "snapshot '$snapname' does not exist\n"
3069 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3071 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3073 die "parameter 'storage' not allowed for linked clones\n"
3074 if defined($storage) && !$full;
3076 die "parameter 'format' not allowed for linked clones\n"
3077 if defined($format) && !$full;
3079 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3081 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3083 die "can't clone VM to node '$target' (VM uses local storage)\n"
3084 if $target && !$sharedvm;
3086 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3087 die "unable to create VM $newid: config file already exists\n"
3090 my $newconf = { lock => 'clone' };
3095 foreach my $opt (keys %$oldconf) {
3096 my $value = $oldconf->{$opt};
3098 # do not copy snapshot related info
3099 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3100 $opt eq 'vmstate' || $opt eq 'snapstate';
3102 # no need to copy unused images, because VMID(owner) changes anyways
3103 next if $opt =~ m/^unused\d+$/;
3105 # always change MAC! address
3106 if ($opt =~ m/^net(\d+)$/) {
3107 my $net = PVE
::QemuServer
::parse_net
($value);
3108 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3109 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3110 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3111 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3112 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3113 die "unable to parse drive options for '$opt'\n" if !$drive;
3114 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3115 $newconf->{$opt} = $value; # simply copy configuration
3117 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3118 die "Full clone feature is not supported for drive '$opt'\n"
3119 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3120 $fullclone->{$opt} = 1;
3122 # not full means clone instead of copy
3123 die "Linked clone feature is not supported for drive '$opt'\n"
3124 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3126 $drives->{$opt} = $drive;
3127 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3128 push @$vollist, $drive->{file
};
3131 # copy everything else
3132 $newconf->{$opt} = $value;
3136 # auto generate a new uuid
3137 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3138 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3139 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3140 # auto generate a new vmgenid only if the option was set for template
3141 if ($newconf->{vmgenid
}) {
3142 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3145 delete $newconf->{template
};
3147 if ($param->{name
}) {
3148 $newconf->{name
} = $param->{name
};
3150 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3153 if ($param->{description
}) {
3154 $newconf->{description
} = $param->{description
};
3157 # create empty/temp config - this fails if VM already exists on other node
3158 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3159 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3164 my $newvollist = [];
3171 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3173 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3175 my $bwlimit = extract_param
($param, 'bwlimit');
3177 my $total_jobs = scalar(keys %{$drives});
3180 foreach my $opt (sort keys %$drives) {
3181 my $drive = $drives->{$opt};
3182 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3183 my $completion = $skipcomplete ?
'skip' : 'complete';
3185 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3186 my $storage_list = [ $src_sid ];
3187 push @$storage_list, $storage if defined($storage);
3188 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3190 my $newdrive = PVE
::QemuServer
::clone_disk
(
3209 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3211 PVE
::QemuConfig-
>write_config($newid, $newconf);
3215 delete $newconf->{lock};
3217 # do not write pending changes
3218 if (my @changes = keys %{$newconf->{pending
}}) {
3219 my $pending = join(',', @changes);
3220 warn "found pending changes for '$pending', discarding for clone\n";
3221 delete $newconf->{pending
};
3224 PVE
::QemuConfig-
>write_config($newid, $newconf);
3227 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3228 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3229 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3231 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3232 die "Failed to move config to node '$target' - rename failed: $!\n"
3233 if !rename($conffile, $newconffile);
3236 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3239 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3240 sleep 1; # some storage like rbd need to wait before release volume - really?
3242 foreach my $volid (@$newvollist) {
3243 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3247 PVE
::Firewall
::remove_vmfw_conf
($newid);
3249 unlink $conffile; # avoid races -> last thing before die
3251 die "clone failed: $err";
3257 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3259 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3262 # Aquire exclusive lock lock for $newid
3263 my $lock_target_vm = sub {
3264 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3267 # exclusive lock if VM is running - else shared lock is enough;
3269 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3271 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3275 __PACKAGE__-
>register_method({
3276 name
=> 'move_vm_disk',
3277 path
=> '{vmid}/move_disk',
3281 description
=> "Move volume to different storage.",
3283 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3285 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3286 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3290 additionalProperties
=> 0,
3292 node
=> get_standard_option
('pve-node'),
3293 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3296 description
=> "The disk you want to move.",
3297 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3299 storage
=> get_standard_option
('pve-storage-id', {
3300 description
=> "Target storage.",
3301 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3305 description
=> "Target Format.",
3306 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3311 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3317 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3322 description
=> "Override I/O bandwidth limit (in KiB/s).",
3326 default => 'move limit from datacenter or storage config',
3332 description
=> "the task ID.",
3337 my $rpcenv = PVE
::RPCEnvironment
::get
();
3338 my $authuser = $rpcenv->get_user();
3340 my $node = extract_param
($param, 'node');
3341 my $vmid = extract_param
($param, 'vmid');
3342 my $digest = extract_param
($param, 'digest');
3343 my $disk = extract_param
($param, 'disk');
3344 my $storeid = extract_param
($param, 'storage');
3345 my $format = extract_param
($param, 'format');
3347 my $storecfg = PVE
::Storage
::config
();
3349 my $updatefn = sub {
3350 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3351 PVE
::QemuConfig-
>check_lock($conf);
3353 die "VM config checksum missmatch (file change by other user?)\n"
3354 if $digest && $digest ne $conf->{digest
};
3356 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3358 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3360 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3361 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3363 my $old_volid = $drive->{file
};
3365 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3366 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3370 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3371 (!$format || !$oldfmt || $oldfmt eq $format);
3373 # this only checks snapshots because $disk is passed!
3374 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3375 die "you can't move a disk with snapshots and delete the source\n"
3376 if $snapshotted && $param->{delete};
3378 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3380 my $running = PVE
::QemuServer
::check_running
($vmid);
3382 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3385 my $newvollist = [];
3391 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3393 warn "moving disk with snapshots, snapshots will not be moved!\n"
3396 my $bwlimit = extract_param
($param, 'bwlimit');
3397 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3399 my $newdrive = PVE
::QemuServer
::clone_disk
(
3417 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3419 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3421 # convert moved disk to base if part of template
3422 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3423 if PVE
::QemuConfig-
>is_template($conf);
3425 PVE
::QemuConfig-
>write_config($vmid, $conf);
3427 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3428 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3429 eval { mon_cmd
($vmid, "guest-fstrim") };
3433 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3434 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3440 foreach my $volid (@$newvollist) {
3441 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3444 die "storage migration failed: $err";
3447 if ($param->{delete}) {
3449 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3450 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3456 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3459 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3462 my $check_vm_disks_local = sub {
3463 my ($storecfg, $vmconf, $vmid) = @_;
3465 my $local_disks = {};
3467 # add some more information to the disks e.g. cdrom
3468 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3469 my ($volid, $attr) = @_;
3471 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3473 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3474 return if $scfg->{shared
};
3476 # The shared attr here is just a special case where the vdisk
3477 # is marked as shared manually
3478 return if $attr->{shared
};
3479 return if $attr->{cdrom
} and $volid eq "none";
3481 if (exists $local_disks->{$volid}) {
3482 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3484 $local_disks->{$volid} = $attr;
3485 # ensure volid is present in case it's needed
3486 $local_disks->{$volid}->{volid
} = $volid;
3490 return $local_disks;
3493 __PACKAGE__-
>register_method({
3494 name
=> 'migrate_vm_precondition',
3495 path
=> '{vmid}/migrate',
3499 description
=> "Get preconditions for migration.",
3501 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3504 additionalProperties
=> 0,
3506 node
=> get_standard_option
('pve-node'),
3507 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3508 target
=> get_standard_option
('pve-node', {
3509 description
=> "Target node.",
3510 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3518 running
=> { type
=> 'boolean' },
3522 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3524 not_allowed_nodes
=> {
3527 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3531 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3533 local_resources
=> {
3535 description
=> "List local resources e.g. pci, usb"
3542 my $rpcenv = PVE
::RPCEnvironment
::get
();
3544 my $authuser = $rpcenv->get_user();
3546 PVE
::Cluster
::check_cfs_quorum
();
3550 my $vmid = extract_param
($param, 'vmid');
3551 my $target = extract_param
($param, 'target');
3552 my $localnode = PVE
::INotify
::nodename
();
3556 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3557 my $storecfg = PVE
::Storage
::config
();
3560 # try to detect errors early
3561 PVE
::QemuConfig-
>check_lock($vmconf);
3563 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3565 # if vm is not running, return target nodes where local storage is available
3566 # for offline migration
3567 if (!$res->{running
}) {
3568 $res->{allowed_nodes
} = [];
3569 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3570 delete $checked_nodes->{$localnode};
3572 foreach my $node (keys %$checked_nodes) {
3573 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3574 push @{$res->{allowed_nodes
}}, $node;
3578 $res->{not_allowed_nodes
} = $checked_nodes;
3582 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3583 $res->{local_disks
} = [ values %$local_disks ];;
3585 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3587 $res->{local_resources
} = $local_resources;
3594 __PACKAGE__-
>register_method({
3595 name
=> 'migrate_vm',
3596 path
=> '{vmid}/migrate',
3600 description
=> "Migrate virtual machine. Creates a new migration task.",
3602 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3605 additionalProperties
=> 0,
3607 node
=> get_standard_option
('pve-node'),
3608 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3609 target
=> get_standard_option
('pve-node', {
3610 description
=> "Target node.",
3611 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3615 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3620 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3625 enum
=> ['secure', 'insecure'],
3626 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3629 migration_network
=> {
3630 type
=> 'string', format
=> 'CIDR',
3631 description
=> "CIDR of the (sub) network that is used for migration.",
3634 "with-local-disks" => {
3636 description
=> "Enable live storage migration for local disk",
3639 targetstorage
=> get_standard_option
('pve-targetstorage', {
3640 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3643 description
=> "Override I/O bandwidth limit (in KiB/s).",
3647 default => 'migrate limit from datacenter or storage config',
3653 description
=> "the task ID.",
3658 my $rpcenv = PVE
::RPCEnvironment
::get
();
3659 my $authuser = $rpcenv->get_user();
3661 my $target = extract_param
($param, 'target');
3663 my $localnode = PVE
::INotify
::nodename
();
3664 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3666 PVE
::Cluster
::check_cfs_quorum
();
3668 PVE
::Cluster
::check_node_exists
($target);
3670 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3672 my $vmid = extract_param
($param, 'vmid');
3674 raise_param_exc
({ force
=> "Only root may use this option." })
3675 if $param->{force
} && $authuser ne 'root@pam';
3677 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3678 if $param->{migration_type
} && $authuser ne 'root@pam';
3680 # allow root only until better network permissions are available
3681 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3682 if $param->{migration_network
} && $authuser ne 'root@pam';
3685 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3687 # try to detect errors early
3689 PVE
::QemuConfig-
>check_lock($conf);
3691 if (PVE
::QemuServer
::check_running
($vmid)) {
3692 die "can't migrate running VM without --online\n" if !$param->{online
};
3694 my $repl_conf = PVE
::ReplicationConfig-
>new();
3695 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3696 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3697 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3698 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3699 "target. Use 'force' to override.\n";
3702 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3703 $param->{online
} = 0;
3706 my $storecfg = PVE
::Storage
::config
();
3708 if (my $targetstorage = $param->{targetstorage
}) {
3709 my $check_storage = sub {
3710 my ($target_sid) = @_;
3711 PVE
::Storage
::storage_check_enabled
($storecfg, $target_sid, $target);
3712 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3713 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3714 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3715 if !$scfg->{content
}->{images
};
3718 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3719 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3722 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3723 if !defined($storagemap->{identity
});
3725 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3726 $check_storage->($target_sid);
3729 $check_storage->($storagemap->{default})
3730 if $storagemap->{default};
3732 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3733 if $storagemap->{identity
};
3735 $param->{storagemap
} = $storagemap;
3737 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3740 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3745 print "Requesting HA migration for VM $vmid to node $target\n";
3747 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3748 PVE
::Tools
::run_command
($cmd);
3752 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3757 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3761 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3764 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3769 __PACKAGE__-
>register_method({
3771 path
=> '{vmid}/monitor',
3775 description
=> "Execute Qemu monitor commands.",
3777 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3778 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3781 additionalProperties
=> 0,
3783 node
=> get_standard_option
('pve-node'),
3784 vmid
=> get_standard_option
('pve-vmid'),
3787 description
=> "The monitor command.",
3791 returns
=> { type
=> 'string'},
3795 my $rpcenv = PVE
::RPCEnvironment
::get
();
3796 my $authuser = $rpcenv->get_user();
3799 my $command = shift;
3800 return $command =~ m/^\s*info(\s+|$)/
3801 || $command =~ m/^\s*help\s*$/;
3804 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3805 if !&$is_ro($param->{command
});
3807 my $vmid = $param->{vmid
};
3809 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3813 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3815 $res = "ERROR: $@" if $@;
3820 __PACKAGE__-
>register_method({
3821 name
=> 'resize_vm',
3822 path
=> '{vmid}/resize',
3826 description
=> "Extend volume size.",
3828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3831 additionalProperties
=> 0,
3833 node
=> get_standard_option
('pve-node'),
3834 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3835 skiplock
=> get_standard_option
('skiplock'),
3838 description
=> "The disk you want to resize.",
3839 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3843 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3844 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.",
3848 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3854 returns
=> { type
=> 'null'},
3858 my $rpcenv = PVE
::RPCEnvironment
::get
();
3860 my $authuser = $rpcenv->get_user();
3862 my $node = extract_param
($param, 'node');
3864 my $vmid = extract_param
($param, 'vmid');
3866 my $digest = extract_param
($param, 'digest');
3868 my $disk = extract_param
($param, 'disk');
3870 my $sizestr = extract_param
($param, 'size');
3872 my $skiplock = extract_param
($param, 'skiplock');
3873 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3874 if $skiplock && $authuser ne 'root@pam';
3876 my $storecfg = PVE
::Storage
::config
();
3878 my $updatefn = sub {
3880 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3882 die "checksum missmatch (file change by other user?)\n"
3883 if $digest && $digest ne $conf->{digest
};
3884 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3886 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3888 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3890 my (undef, undef, undef, undef, undef, undef, $format) =
3891 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3893 die "can't resize volume: $disk if snapshot exists\n"
3894 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3896 my $volid = $drive->{file
};
3898 die "disk '$disk' has no associated volume\n" if !$volid;
3900 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3902 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3904 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3906 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3907 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3909 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3911 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3912 my ($ext, $newsize, $unit) = ($1, $2, $4);
3915 $newsize = $newsize * 1024;
3916 } elsif ($unit eq 'M') {
3917 $newsize = $newsize * 1024 * 1024;
3918 } elsif ($unit eq 'G') {
3919 $newsize = $newsize * 1024 * 1024 * 1024;
3920 } elsif ($unit eq 'T') {
3921 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3924 $newsize += $size if $ext;
3925 $newsize = int($newsize);
3927 die "shrinking disks is not supported\n" if $newsize < $size;
3929 return if $size == $newsize;
3931 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3933 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3935 $drive->{size
} = $newsize;
3936 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3938 PVE
::QemuConfig-
>write_config($vmid, $conf);
3941 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3945 __PACKAGE__-
>register_method({
3946 name
=> 'snapshot_list',
3947 path
=> '{vmid}/snapshot',
3949 description
=> "List all snapshots.",
3951 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3954 protected
=> 1, # qemu pid files are only readable by root
3956 additionalProperties
=> 0,
3958 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3959 node
=> get_standard_option
('pve-node'),
3968 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3972 description
=> "Snapshot includes RAM.",
3977 description
=> "Snapshot description.",
3981 description
=> "Snapshot creation time",
3983 renderer
=> 'timestamp',
3987 description
=> "Parent snapshot identifier.",
3993 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3998 my $vmid = $param->{vmid
};
4000 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4001 my $snaphash = $conf->{snapshots
} || {};
4005 foreach my $name (keys %$snaphash) {
4006 my $d = $snaphash->{$name};
4009 snaptime
=> $d->{snaptime
} || 0,
4010 vmstate
=> $d->{vmstate
} ?
1 : 0,
4011 description
=> $d->{description
} || '',
4013 $item->{parent
} = $d->{parent
} if $d->{parent
};
4014 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4018 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4021 digest
=> $conf->{digest
},
4022 running
=> $running,
4023 description
=> "You are here!",
4025 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4027 push @$res, $current;
4032 __PACKAGE__-
>register_method({
4034 path
=> '{vmid}/snapshot',
4038 description
=> "Snapshot a VM.",
4040 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4043 additionalProperties
=> 0,
4045 node
=> get_standard_option
('pve-node'),
4046 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4047 snapname
=> get_standard_option
('pve-snapshot-name'),
4051 description
=> "Save the vmstate",
4056 description
=> "A textual description or comment.",
4062 description
=> "the task ID.",
4067 my $rpcenv = PVE
::RPCEnvironment
::get
();
4069 my $authuser = $rpcenv->get_user();
4071 my $node = extract_param
($param, 'node');
4073 my $vmid = extract_param
($param, 'vmid');
4075 my $snapname = extract_param
($param, 'snapname');
4077 die "unable to use snapshot name 'current' (reserved name)\n"
4078 if $snapname eq 'current';
4080 die "unable to use snapshot name 'pending' (reserved name)\n"
4081 if lc($snapname) eq 'pending';
4084 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4085 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4086 $param->{description
});
4089 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4092 __PACKAGE__-
>register_method({
4093 name
=> 'snapshot_cmd_idx',
4094 path
=> '{vmid}/snapshot/{snapname}',
4101 additionalProperties
=> 0,
4103 vmid
=> get_standard_option
('pve-vmid'),
4104 node
=> get_standard_option
('pve-node'),
4105 snapname
=> get_standard_option
('pve-snapshot-name'),
4114 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4121 push @$res, { cmd
=> 'rollback' };
4122 push @$res, { cmd
=> 'config' };
4127 __PACKAGE__-
>register_method({
4128 name
=> 'update_snapshot_config',
4129 path
=> '{vmid}/snapshot/{snapname}/config',
4133 description
=> "Update snapshot metadata.",
4135 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4138 additionalProperties
=> 0,
4140 node
=> get_standard_option
('pve-node'),
4141 vmid
=> get_standard_option
('pve-vmid'),
4142 snapname
=> get_standard_option
('pve-snapshot-name'),
4146 description
=> "A textual description or comment.",
4150 returns
=> { type
=> 'null' },
4154 my $rpcenv = PVE
::RPCEnvironment
::get
();
4156 my $authuser = $rpcenv->get_user();
4158 my $vmid = extract_param
($param, 'vmid');
4160 my $snapname = extract_param
($param, 'snapname');
4162 return if !defined($param->{description
});
4164 my $updatefn = sub {
4166 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4168 PVE
::QemuConfig-
>check_lock($conf);
4170 my $snap = $conf->{snapshots
}->{$snapname};
4172 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4174 $snap->{description
} = $param->{description
} if defined($param->{description
});
4176 PVE
::QemuConfig-
>write_config($vmid, $conf);
4179 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4184 __PACKAGE__-
>register_method({
4185 name
=> 'get_snapshot_config',
4186 path
=> '{vmid}/snapshot/{snapname}/config',
4189 description
=> "Get snapshot configuration",
4191 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4194 additionalProperties
=> 0,
4196 node
=> get_standard_option
('pve-node'),
4197 vmid
=> get_standard_option
('pve-vmid'),
4198 snapname
=> get_standard_option
('pve-snapshot-name'),
4201 returns
=> { type
=> "object" },
4205 my $rpcenv = PVE
::RPCEnvironment
::get
();
4207 my $authuser = $rpcenv->get_user();
4209 my $vmid = extract_param
($param, 'vmid');
4211 my $snapname = extract_param
($param, 'snapname');
4213 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4215 my $snap = $conf->{snapshots
}->{$snapname};
4217 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4222 __PACKAGE__-
>register_method({
4224 path
=> '{vmid}/snapshot/{snapname}/rollback',
4228 description
=> "Rollback VM state to specified snapshot.",
4230 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4233 additionalProperties
=> 0,
4235 node
=> get_standard_option
('pve-node'),
4236 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4237 snapname
=> get_standard_option
('pve-snapshot-name'),
4242 description
=> "the task ID.",
4247 my $rpcenv = PVE
::RPCEnvironment
::get
();
4249 my $authuser = $rpcenv->get_user();
4251 my $node = extract_param
($param, 'node');
4253 my $vmid = extract_param
($param, 'vmid');
4255 my $snapname = extract_param
($param, 'snapname');
4258 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4259 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4263 # hold migration lock, this makes sure that nobody create replication snapshots
4264 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4267 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4270 __PACKAGE__-
>register_method({
4271 name
=> 'delsnapshot',
4272 path
=> '{vmid}/snapshot/{snapname}',
4276 description
=> "Delete a VM snapshot.",
4278 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4281 additionalProperties
=> 0,
4283 node
=> get_standard_option
('pve-node'),
4284 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4285 snapname
=> get_standard_option
('pve-snapshot-name'),
4289 description
=> "For removal from config file, even if removing disk snapshots fails.",
4295 description
=> "the task ID.",
4300 my $rpcenv = PVE
::RPCEnvironment
::get
();
4302 my $authuser = $rpcenv->get_user();
4304 my $node = extract_param
($param, 'node');
4306 my $vmid = extract_param
($param, 'vmid');
4308 my $snapname = extract_param
($param, 'snapname');
4311 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4312 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4315 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4318 __PACKAGE__-
>register_method({
4320 path
=> '{vmid}/template',
4324 description
=> "Create a Template.",
4326 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4327 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4330 additionalProperties
=> 0,
4332 node
=> get_standard_option
('pve-node'),
4333 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4337 description
=> "If you want to convert only 1 disk to base image.",
4338 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4345 description
=> "the task ID.",
4350 my $rpcenv = PVE
::RPCEnvironment
::get
();
4352 my $authuser = $rpcenv->get_user();
4354 my $node = extract_param
($param, 'node');
4356 my $vmid = extract_param
($param, 'vmid');
4358 my $disk = extract_param
($param, 'disk');
4360 my $load_and_check = sub {
4361 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4363 PVE
::QemuConfig-
>check_lock($conf);
4365 die "unable to create template, because VM contains snapshots\n"
4366 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4368 die "you can't convert a template to a template\n"
4369 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4371 die "you can't convert a VM to template if VM is running\n"
4372 if PVE
::QemuServer
::check_running
($vmid);
4377 $load_and_check->();
4380 PVE
::QemuConfig-
>lock_config($vmid, sub {
4381 my $conf = $load_and_check->();
4383 $conf->{template
} = 1;
4384 PVE
::QemuConfig-
>write_config($vmid, $conf);
4386 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4390 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4393 __PACKAGE__-
>register_method({
4394 name
=> 'cloudinit_generated_config_dump',
4395 path
=> '{vmid}/cloudinit/dump',
4398 description
=> "Get automatically generated cloudinit config.",
4400 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4403 additionalProperties
=> 0,
4405 node
=> get_standard_option
('pve-node'),
4406 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4408 description
=> 'Config type.',
4410 enum
=> ['user', 'network', 'meta'],
4420 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4422 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});