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
($storecfg, $storeid, $vmid, $fmt, $arch);
188 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
190 push @$vollist, $volid;
191 $disk->{file
} = $volid;
192 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
193 delete $disk->{format
}; # no longer needed
194 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
197 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
199 my $volid_is_new = 1;
202 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
203 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
208 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
210 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
212 die "volume $volid does not exist\n" if !$size;
214 $disk->{size
} = $size;
217 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
221 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
223 # free allocated images on error
225 syslog
('err', "VM $vmid creating disks failed");
226 foreach my $volid (@$vollist) {
227 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
233 # modify vm config if everything went well
234 foreach my $ds (keys %$res) {
235 $conf->{$ds} = $res->{$ds};
241 my $check_cpu_model_access = sub {
242 my ($rpcenv, $authuser, $new, $existing) = @_;
244 return if !defined($new->{cpu
});
246 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
247 return if !$cpu || !$cpu->{cputype
}; # always allow default
248 my $cputype = $cpu->{cputype
};
250 if ($existing && $existing->{cpu
}) {
251 # changing only other settings doesn't require permissions for CPU model
252 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
253 return if $existingCpu->{cputype
} eq $cputype;
256 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
257 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
272 my $memoryoptions = {
278 my $hwtypeoptions = {
291 my $generaloptions = {
298 'migrate_downtime' => 1,
299 'migrate_speed' => 1,
312 my $vmpoweroptions = {
319 'vmstatestorage' => 1,
322 my $cloudinitoptions = {
332 my $check_vm_create_serial_perm = sub {
333 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
335 return 1 if $authuser eq 'root@pam';
337 foreach my $opt (keys %{$param}) {
338 next if $opt !~ m/^serial\d+$/;
340 if ($param->{$opt} eq 'socket') {
341 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
343 die "only root can set '$opt' config for real devices\n";
350 my $check_vm_create_usb_perm = sub {
351 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
353 return 1 if $authuser eq 'root@pam';
355 foreach my $opt (keys %{$param}) {
356 next if $opt !~ m/^usb\d+$/;
358 if ($param->{$opt} =~ m/spice/) {
359 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
361 die "only root can set '$opt' config for real devices\n";
368 my $check_vm_modify_config_perm = sub {
369 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
371 return 1 if $authuser eq 'root@pam';
373 foreach my $opt (@$key_list) {
374 # some checks (e.g., disk, serial port, usb) need to be done somewhere
375 # else, as there the permission can be value dependend
376 next if PVE
::QemuServer
::is_valid_drivename
($opt);
377 next if $opt eq 'cdrom';
378 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
381 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
382 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
383 } elsif ($memoryoptions->{$opt}) {
384 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
385 } elsif ($hwtypeoptions->{$opt}) {
386 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
387 } elsif ($generaloptions->{$opt}) {
388 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
389 # special case for startup since it changes host behaviour
390 if ($opt eq 'startup') {
391 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
393 } elsif ($vmpoweroptions->{$opt}) {
394 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
395 } elsif ($diskoptions->{$opt}) {
396 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
397 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
398 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
399 } elsif ($cloudinitoptions->{$opt}) {
400 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
401 } elsif ($opt eq 'vmstate') {
402 # the user needs Disk and PowerMgmt privileges to change the vmstate
403 # also needs privileges on the storage, that will be checked later
404 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
406 # catches hostpci\d+, args, lock, etc.
407 # new options will be checked here
408 die "only root can set '$opt' config\n";
415 __PACKAGE__-
>register_method({
419 description
=> "Virtual machine index (per node).",
421 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
425 protected
=> 1, # qemu pid files are only readable by root
427 additionalProperties
=> 0,
429 node
=> get_standard_option
('pve-node'),
433 description
=> "Determine the full status of active VMs.",
441 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
443 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
448 my $rpcenv = PVE
::RPCEnvironment
::get
();
449 my $authuser = $rpcenv->get_user();
451 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
454 foreach my $vmid (keys %$vmstatus) {
455 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
457 my $data = $vmstatus->{$vmid};
464 my $parse_restore_archive = sub {
465 my ($storecfg, $archive) = @_;
467 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
469 if (defined($archive_storeid)) {
470 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
471 if ($scfg->{type
} eq 'pbs') {
478 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
486 __PACKAGE__-
>register_method({
490 description
=> "Create or restore a virtual machine.",
492 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
493 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
494 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
495 user
=> 'all', # check inside
500 additionalProperties
=> 0,
501 properties
=> PVE
::QemuServer
::json_config_properties
(
503 node
=> get_standard_option
('pve-node'),
504 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
506 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.",
510 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
512 storage
=> get_standard_option
('pve-storage-id', {
513 description
=> "Default storage.",
515 completion
=> \
&PVE
::QemuServer
::complete_storage
,
520 description
=> "Allow to overwrite existing VM.",
521 requires
=> 'archive',
526 description
=> "Assign a unique random ethernet address.",
527 requires
=> 'archive',
532 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
533 requires
=> 'archive',
537 type
=> 'string', format
=> 'pve-poolid',
538 description
=> "Add the VM to the specified pool.",
541 description
=> "Override I/O bandwidth limit (in KiB/s).",
545 default => 'restore limit from datacenter or storage config',
551 description
=> "Start VM after it was created successfully.",
561 my $rpcenv = PVE
::RPCEnvironment
::get
();
562 my $authuser = $rpcenv->get_user();
564 my $node = extract_param
($param, 'node');
565 my $vmid = extract_param
($param, 'vmid');
567 my $archive = extract_param
($param, 'archive');
568 my $is_restore = !!$archive;
570 my $bwlimit = extract_param
($param, 'bwlimit');
571 my $force = extract_param
($param, 'force');
572 my $pool = extract_param
($param, 'pool');
573 my $start_after_create = extract_param
($param, 'start');
574 my $storage = extract_param
($param, 'storage');
575 my $unique = extract_param
($param, 'unique');
576 my $live_restore = extract_param
($param, 'live-restore');
578 if (defined(my $ssh_keys = $param->{sshkeys
})) {
579 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
580 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
583 PVE
::Cluster
::check_cfs_quorum
();
585 my $filename = PVE
::QemuConfig-
>config_file($vmid);
586 my $storecfg = PVE
::Storage
::config
();
588 if (defined($pool)) {
589 $rpcenv->check_pool_exist($pool);
592 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
593 if defined($storage);
595 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
597 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
599 } elsif ($archive && $force && (-f
$filename) &&
600 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
601 # OK: user has VM.Backup permissions, and want to restore an existing VM
607 &$resolve_cdrom_alias($param);
609 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
611 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
613 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
614 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
616 &$check_cpu_model_access($rpcenv, $authuser, $param);
618 foreach my $opt (keys %$param) {
619 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
620 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
621 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
623 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
624 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
628 PVE
::QemuServer
::add_random_macs
($param);
630 my $keystr = join(' ', keys %$param);
631 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
633 if ($archive eq '-') {
634 die "pipe requires cli environment\n"
635 if $rpcenv->{type
} ne 'cli';
636 $archive = { type
=> 'pipe' };
638 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
640 $archive = $parse_restore_archive->($storecfg, $archive);
644 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
646 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
647 die "$emsg $@" if $@;
649 my $restored_data = 0;
650 my $restorefn = sub {
651 my $conf = PVE
::QemuConfig-
>load_config($vmid);
653 PVE
::QemuConfig-
>check_protection($conf, $emsg);
655 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
658 my $restore_options = {
663 live
=> $live_restore,
665 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
666 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
668 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
669 } elsif ($archive->{type
} eq 'pbs') {
670 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
672 die "unknown backup archive type\n";
676 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
677 # Convert restored VM to template if backup was VM template
678 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
679 warn "Convert to template.\n";
680 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
685 # ensure no old replication state are exists
686 PVE
::ReplicationState
::delete_guest_states
($vmid);
688 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
690 if ($start_after_create && !$live_restore) {
691 print "Execute autostart\n";
692 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
698 # ensure no old replication state are exists
699 PVE
::ReplicationState
::delete_guest_states
($vmid);
703 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
707 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
709 if (!$conf->{boot
}) {
710 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
711 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
714 # auto generate uuid if user did not specify smbios1 option
715 if (!$conf->{smbios1
}) {
716 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
719 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
720 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
723 my $machine = $conf->{machine
};
724 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
725 # always pin Windows' machine version on create, they get to easily confused
726 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
727 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
731 PVE
::QemuConfig-
>write_config($vmid, $conf);
737 foreach my $volid (@$vollist) {
738 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
744 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
747 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
749 if ($start_after_create) {
750 print "Execute autostart\n";
751 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
756 my ($code, $worker_name);
758 $worker_name = 'qmrestore';
760 eval { $restorefn->() };
762 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
764 if ($restored_data) {
765 warn "error after data was restored, VM disks should be OK but config may "
766 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
768 warn "error before or during data restore, some or all disks were not "
769 ."completely restored. VM $vmid state is NOT cleaned up.\n";
775 $worker_name = 'qmcreate';
777 eval { $createfn->() };
780 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
781 unlink($conffile) or die "failed to remove config file: $!\n";
789 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
792 __PACKAGE__-
>register_method({
797 description
=> "Directory index",
802 additionalProperties
=> 0,
804 node
=> get_standard_option
('pve-node'),
805 vmid
=> get_standard_option
('pve-vmid'),
813 subdir
=> { type
=> 'string' },
816 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
822 { subdir
=> 'config' },
823 { subdir
=> 'pending' },
824 { subdir
=> 'status' },
825 { subdir
=> 'unlink' },
826 { subdir
=> 'vncproxy' },
827 { subdir
=> 'termproxy' },
828 { subdir
=> 'migrate' },
829 { subdir
=> 'resize' },
830 { subdir
=> 'move' },
832 { subdir
=> 'rrddata' },
833 { subdir
=> 'monitor' },
834 { subdir
=> 'agent' },
835 { subdir
=> 'snapshot' },
836 { subdir
=> 'spiceproxy' },
837 { subdir
=> 'sendkey' },
838 { subdir
=> 'firewall' },
844 __PACKAGE__-
>register_method ({
845 subclass
=> "PVE::API2::Firewall::VM",
846 path
=> '{vmid}/firewall',
849 __PACKAGE__-
>register_method ({
850 subclass
=> "PVE::API2::Qemu::Agent",
851 path
=> '{vmid}/agent',
854 __PACKAGE__-
>register_method({
856 path
=> '{vmid}/rrd',
858 protected
=> 1, # fixme: can we avoid that?
860 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
862 description
=> "Read VM RRD statistics (returns PNG)",
864 additionalProperties
=> 0,
866 node
=> get_standard_option
('pve-node'),
867 vmid
=> get_standard_option
('pve-vmid'),
869 description
=> "Specify the time frame you are interested in.",
871 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
874 description
=> "The list of datasources you want to display.",
875 type
=> 'string', format
=> 'pve-configid-list',
878 description
=> "The RRD consolidation function",
880 enum
=> [ 'AVERAGE', 'MAX' ],
888 filename
=> { type
=> 'string' },
894 return PVE
::RRD
::create_rrd_graph
(
895 "pve2-vm/$param->{vmid}", $param->{timeframe
},
896 $param->{ds
}, $param->{cf
});
900 __PACKAGE__-
>register_method({
902 path
=> '{vmid}/rrddata',
904 protected
=> 1, # fixme: can we avoid that?
906 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
908 description
=> "Read VM RRD statistics",
910 additionalProperties
=> 0,
912 node
=> get_standard_option
('pve-node'),
913 vmid
=> get_standard_option
('pve-vmid'),
915 description
=> "Specify the time frame you are interested in.",
917 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
920 description
=> "The RRD consolidation function",
922 enum
=> [ 'AVERAGE', 'MAX' ],
937 return PVE
::RRD
::create_rrd_data
(
938 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
942 __PACKAGE__-
>register_method({
944 path
=> '{vmid}/config',
947 description
=> "Get the virtual machine configuration with pending configuration " .
948 "changes applied. Set the 'current' parameter to get the current configuration instead.",
950 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
953 additionalProperties
=> 0,
955 node
=> get_standard_option
('pve-node'),
956 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
958 description
=> "Get current values (instead of pending values).",
963 snapshot
=> get_standard_option
('pve-snapshot-name', {
964 description
=> "Fetch config values from given snapshot.",
967 my ($cmd, $pname, $cur, $args) = @_;
968 PVE
::QemuConfig-
>snapshot_list($args->[0]);
974 description
=> "The VM configuration.",
976 properties
=> PVE
::QemuServer
::json_config_properties
({
979 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
986 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
987 current
=> "cannot use 'snapshot' parameter with 'current'"})
988 if ($param->{snapshot
} && $param->{current
});
991 if ($param->{snapshot
}) {
992 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
994 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
996 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1001 __PACKAGE__-
>register_method({
1002 name
=> 'vm_pending',
1003 path
=> '{vmid}/pending',
1006 description
=> "Get the virtual machine configuration with both current and pending values.",
1008 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1011 additionalProperties
=> 0,
1013 node
=> get_standard_option
('pve-node'),
1014 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1023 description
=> "Configuration option name.",
1027 description
=> "Current value.",
1032 description
=> "Pending value.",
1037 description
=> "Indicates a pending delete request if present and not 0. " .
1038 "The value 2 indicates a force-delete request.",
1050 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1052 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1054 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1055 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1057 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1060 # POST/PUT {vmid}/config implementation
1062 # The original API used PUT (idempotent) an we assumed that all operations
1063 # are fast. But it turned out that almost any configuration change can
1064 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1065 # time to complete and have side effects (not idempotent).
1067 # The new implementation uses POST and forks a worker process. We added
1068 # a new option 'background_delay'. If specified we wait up to
1069 # 'background_delay' second for the worker task to complete. It returns null
1070 # if the task is finished within that time, else we return the UPID.
1072 my $update_vm_api = sub {
1073 my ($param, $sync) = @_;
1075 my $rpcenv = PVE
::RPCEnvironment
::get
();
1077 my $authuser = $rpcenv->get_user();
1079 my $node = extract_param
($param, 'node');
1081 my $vmid = extract_param
($param, 'vmid');
1083 my $digest = extract_param
($param, 'digest');
1085 my $background_delay = extract_param
($param, 'background_delay');
1087 if (defined(my $cipassword = $param->{cipassword
})) {
1088 # Same logic as in cloud-init (but with the regex fixed...)
1089 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1090 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1093 my @paramarr = (); # used for log message
1094 foreach my $key (sort keys %$param) {
1095 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1096 push @paramarr, "-$key", $value;
1099 my $skiplock = extract_param
($param, 'skiplock');
1100 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1101 if $skiplock && $authuser ne 'root@pam';
1103 my $delete_str = extract_param
($param, 'delete');
1105 my $revert_str = extract_param
($param, 'revert');
1107 my $force = extract_param
($param, 'force');
1109 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1110 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1111 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1114 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1116 my $storecfg = PVE
::Storage
::config
();
1118 my $defaults = PVE
::QemuServer
::load_defaults
();
1120 &$resolve_cdrom_alias($param);
1122 # now try to verify all parameters
1125 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1126 if (!PVE
::QemuServer
::option_exists
($opt)) {
1127 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1130 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1131 "-revert $opt' at the same time" })
1132 if defined($param->{$opt});
1134 $revert->{$opt} = 1;
1138 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1139 $opt = 'ide2' if $opt eq 'cdrom';
1141 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1142 "-delete $opt' at the same time" })
1143 if defined($param->{$opt});
1145 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1146 "-revert $opt' at the same time" })
1149 if (!PVE
::QemuServer
::option_exists
($opt)) {
1150 raise_param_exc
({ delete => "unknown option '$opt'" });
1156 my $repl_conf = PVE
::ReplicationConfig-
>new();
1157 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1158 my $check_replication = sub {
1160 return if !$is_replicated;
1161 my $volid = $drive->{file
};
1162 return if !$volid || !($drive->{replicate
}//1);
1163 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1165 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1166 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1167 if !defined($storeid);
1169 return if defined($volname) && $volname eq 'cloudinit';
1172 if ($volid =~ $NEW_DISK_RE) {
1174 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1176 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1178 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1179 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1180 return if $scfg->{shared
};
1181 die "cannot add non-replicatable volume to a replicated VM\n";
1184 foreach my $opt (keys %$param) {
1185 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1186 # cleanup drive path
1187 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1188 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1189 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1190 $check_replication->($drive);
1191 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1192 } elsif ($opt =~ m/^net(\d+)$/) {
1194 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1195 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1196 } elsif ($opt eq 'vmgenid') {
1197 if ($param->{$opt} eq '1') {
1198 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1200 } elsif ($opt eq 'hookscript') {
1201 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1202 raise_param_exc
({ $opt => $@ }) if $@;
1206 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1208 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1210 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1212 my $updatefn = sub {
1214 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1216 die "checksum missmatch (file change by other user?)\n"
1217 if $digest && $digest ne $conf->{digest
};
1219 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1221 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1222 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1223 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1224 delete $conf->{lock}; # for check lock check, not written out
1225 push @delete, 'lock'; # this is the real deal to write it out
1227 push @delete, 'runningmachine' if $conf->{runningmachine
};
1228 push @delete, 'runningcpu' if $conf->{runningcpu
};
1231 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1233 foreach my $opt (keys %$revert) {
1234 if (defined($conf->{$opt})) {
1235 $param->{$opt} = $conf->{$opt};
1236 } elsif (defined($conf->{pending
}->{$opt})) {
1241 if ($param->{memory
} || defined($param->{balloon
})) {
1242 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1243 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1245 die "balloon value too large (must be smaller than assigned memory)\n"
1246 if $balloon && $balloon > $maxmem;
1249 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1253 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1255 # write updates to pending section
1257 my $modified = {}; # record what $option we modify
1260 if (my $boot = $conf->{boot
}) {
1261 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1262 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1264 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1266 my $check_drive_perms = sub {
1267 my ($opt, $val) = @_;
1268 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1269 # FIXME: cloudinit: CDROM or Disk?
1270 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1271 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1273 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1277 foreach my $opt (@delete) {
1278 $modified->{$opt} = 1;
1279 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1281 # value of what we want to delete, independent if pending or not
1282 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1283 if (!defined($val)) {
1284 warn "cannot delete '$opt' - not set in current configuration!\n";
1285 $modified->{$opt} = 0;
1288 my $is_pending_val = defined($conf->{pending
}->{$opt});
1289 delete $conf->{pending
}->{$opt};
1291 # remove from bootorder if necessary
1292 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1293 @bootorder = grep {$_ ne $opt} @bootorder;
1294 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1295 $modified->{boot
} = 1;
1298 if ($opt =~ m/^unused/) {
1299 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1300 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1301 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1302 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1303 delete $conf->{$opt};
1304 PVE
::QemuConfig-
>write_config($vmid, $conf);
1306 } elsif ($opt eq 'vmstate') {
1307 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1308 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1309 delete $conf->{$opt};
1310 PVE
::QemuConfig-
>write_config($vmid, $conf);
1312 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1313 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1314 $check_drive_perms->($opt, $val);
1315 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1317 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1318 PVE
::QemuConfig-
>write_config($vmid, $conf);
1319 } elsif ($opt =~ m/^serial\d+$/) {
1320 if ($val eq 'socket') {
1321 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1322 } elsif ($authuser ne 'root@pam') {
1323 die "only root can delete '$opt' config for real devices\n";
1325 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1326 PVE
::QemuConfig-
>write_config($vmid, $conf);
1327 } elsif ($opt =~ m/^usb\d+$/) {
1328 if ($val =~ m/spice/) {
1329 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1330 } elsif ($authuser ne 'root@pam') {
1331 die "only root can delete '$opt' config for real devices\n";
1333 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1334 PVE
::QemuConfig-
>write_config($vmid, $conf);
1336 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1337 PVE
::QemuConfig-
>write_config($vmid, $conf);
1341 foreach my $opt (keys %$param) { # add/change
1342 $modified->{$opt} = 1;
1343 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1344 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1346 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1348 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1349 $check_drive_perms->($opt, $param->{$opt});
1350 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1351 if defined($conf->{pending
}->{$opt});
1353 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1354 } elsif ($opt =~ m/^serial\d+/) {
1355 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1356 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1357 } elsif ($authuser ne 'root@pam') {
1358 die "only root can modify '$opt' config for real devices\n";
1360 $conf->{pending
}->{$opt} = $param->{$opt};
1361 } elsif ($opt =~ m/^usb\d+/) {
1362 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1363 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1364 } elsif ($authuser ne 'root@pam') {
1365 die "only root can modify '$opt' config for real devices\n";
1367 $conf->{pending
}->{$opt} = $param->{$opt};
1369 $conf->{pending
}->{$opt} = $param->{$opt};
1371 if ($opt eq 'boot') {
1372 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1373 if ($new_bootcfg->{order
}) {
1374 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1375 for my $dev (@devs) {
1376 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1377 my $deleted = grep {$_ eq $dev} @delete;
1378 die "invalid bootorder: device '$dev' does not exist'\n"
1379 if !$exists || $deleted;
1382 # remove legacy boot order settings if new one set
1383 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1384 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1385 if $conf->{bootdisk
};
1389 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1390 PVE
::QemuConfig-
>write_config($vmid, $conf);
1393 # remove pending changes when nothing changed
1394 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1395 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1396 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1398 return if !scalar(keys %{$conf->{pending
}});
1400 my $running = PVE
::QemuServer
::check_running
($vmid);
1402 # apply pending changes
1404 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1408 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1410 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1412 raise_param_exc
($errors) if scalar(keys %$errors);
1421 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1423 if ($background_delay) {
1425 # Note: It would be better to do that in the Event based HTTPServer
1426 # to avoid blocking call to sleep.
1428 my $end_time = time() + $background_delay;
1430 my $task = PVE
::Tools
::upid_decode
($upid);
1433 while (time() < $end_time) {
1434 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1436 sleep(1); # this gets interrupted when child process ends
1440 my $status = PVE
::Tools
::upid_read_status
($upid);
1441 return if !PVE
::Tools
::upid_status_is_error
($status);
1450 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1453 my $vm_config_perm_list = [
1458 'VM.Config.Network',
1460 'VM.Config.Options',
1461 'VM.Config.Cloudinit',
1464 __PACKAGE__-
>register_method({
1465 name
=> 'update_vm_async',
1466 path
=> '{vmid}/config',
1470 description
=> "Set virtual machine options (asynchrounous API).",
1472 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1475 additionalProperties
=> 0,
1476 properties
=> PVE
::QemuServer
::json_config_properties
(
1478 node
=> get_standard_option
('pve-node'),
1479 vmid
=> get_standard_option
('pve-vmid'),
1480 skiplock
=> get_standard_option
('skiplock'),
1482 type
=> 'string', format
=> 'pve-configid-list',
1483 description
=> "A list of settings you want to delete.",
1487 type
=> 'string', format
=> 'pve-configid-list',
1488 description
=> "Revert a pending change.",
1493 description
=> $opt_force_description,
1495 requires
=> 'delete',
1499 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1503 background_delay
=> {
1505 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1516 code
=> $update_vm_api,
1519 __PACKAGE__-
>register_method({
1520 name
=> 'update_vm',
1521 path
=> '{vmid}/config',
1525 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1527 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1530 additionalProperties
=> 0,
1531 properties
=> PVE
::QemuServer
::json_config_properties
(
1533 node
=> get_standard_option
('pve-node'),
1534 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1535 skiplock
=> get_standard_option
('skiplock'),
1537 type
=> 'string', format
=> 'pve-configid-list',
1538 description
=> "A list of settings you want to delete.",
1542 type
=> 'string', format
=> 'pve-configid-list',
1543 description
=> "Revert a pending change.",
1548 description
=> $opt_force_description,
1550 requires
=> 'delete',
1554 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1560 returns
=> { type
=> 'null' },
1563 &$update_vm_api($param, 1);
1568 __PACKAGE__-
>register_method({
1569 name
=> 'destroy_vm',
1574 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1575 ." and firewall rules",
1577 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1580 additionalProperties
=> 0,
1582 node
=> get_standard_option
('pve-node'),
1583 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1584 skiplock
=> get_standard_option
('skiplock'),
1587 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1590 'destroy-unreferenced-disks' => {
1592 description
=> "If set, destroy additionally all disks not referenced in the config"
1593 ." but with a matching VMID from all enabled storages.",
1605 my $rpcenv = PVE
::RPCEnvironment
::get
();
1606 my $authuser = $rpcenv->get_user();
1607 my $vmid = $param->{vmid
};
1609 my $skiplock = $param->{skiplock
};
1610 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1611 if $skiplock && $authuser ne 'root@pam';
1613 my $early_checks = sub {
1615 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1616 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1618 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1620 if (!$param->{purge
}) {
1621 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1623 # don't allow destroy if with replication jobs but no purge param
1624 my $repl_conf = PVE
::ReplicationConfig-
>new();
1625 $repl_conf->check_for_existing_jobs($vmid);
1628 die "VM $vmid is running - destroy failed\n"
1629 if PVE
::QemuServer
::check_running
($vmid);
1639 my $storecfg = PVE
::Storage
::config
();
1641 syslog
('info', "destroy VM $vmid: $upid\n");
1642 PVE
::QemuConfig-
>lock_config($vmid, sub {
1643 # repeat, config might have changed
1644 my $ha_managed = $early_checks->();
1646 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1648 PVE
::QemuServer
::destroy_vm
(
1651 $skiplock, { lock => 'destroyed' },
1652 $purge_unreferenced,
1655 PVE
::AccessControl
::remove_vm_access
($vmid);
1656 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1657 if ($param->{purge
}) {
1658 print "purging VM $vmid from related configurations..\n";
1659 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1660 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1663 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1664 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1668 # only now remove the zombie config, else we can have reuse race
1669 PVE
::QemuConfig-
>destroy_config($vmid);
1673 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1676 __PACKAGE__-
>register_method({
1678 path
=> '{vmid}/unlink',
1682 description
=> "Unlink/delete disk images.",
1684 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1687 additionalProperties
=> 0,
1689 node
=> get_standard_option
('pve-node'),
1690 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1692 type
=> 'string', format
=> 'pve-configid-list',
1693 description
=> "A list of disk IDs you want to delete.",
1697 description
=> $opt_force_description,
1702 returns
=> { type
=> 'null'},
1706 $param->{delete} = extract_param
($param, 'idlist');
1708 __PACKAGE__-
>update_vm($param);
1713 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1714 my $gen_rand_chars = sub {
1717 die "invalid length $length" if $length < 1;
1719 my $min = ord('!'); # first printable ascii
1721 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1722 die "failed to generate random bytes!\n"
1725 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1732 __PACKAGE__-
>register_method({
1734 path
=> '{vmid}/vncproxy',
1738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1740 description
=> "Creates a TCP VNC proxy connections.",
1742 additionalProperties
=> 0,
1744 node
=> get_standard_option
('pve-node'),
1745 vmid
=> get_standard_option
('pve-vmid'),
1749 description
=> "starts websockify instead of vncproxy",
1751 'generate-password' => {
1755 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1760 additionalProperties
=> 0,
1762 user
=> { type
=> 'string' },
1763 ticket
=> { type
=> 'string' },
1766 description
=> "Returned if requested with 'generate-password' param."
1767 ." Consists of printable ASCII characters ('!' .. '~').",
1770 cert
=> { type
=> 'string' },
1771 port
=> { type
=> 'integer' },
1772 upid
=> { type
=> 'string' },
1778 my $rpcenv = PVE
::RPCEnvironment
::get
();
1780 my $authuser = $rpcenv->get_user();
1782 my $vmid = $param->{vmid
};
1783 my $node = $param->{node
};
1784 my $websocket = $param->{websocket
};
1786 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1790 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1791 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1794 my $authpath = "/vms/$vmid";
1796 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1797 my $password = $ticket;
1798 if ($param->{'generate-password'}) {
1799 $password = $gen_rand_chars->(8);
1802 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1808 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1809 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1810 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1811 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1812 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1814 $family = PVE
::Tools
::get_host_address_family
($node);
1817 my $port = PVE
::Tools
::next_vnc_port
($family);
1824 syslog
('info', "starting vnc proxy $upid\n");
1828 if (defined($serial)) {
1830 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1832 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1833 '-timeout', $timeout, '-authpath', $authpath,
1834 '-perm', 'Sys.Console'];
1836 if ($param->{websocket
}) {
1837 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1838 push @$cmd, '-notls', '-listen', 'localhost';
1841 push @$cmd, '-c', @$remcmd, @$termcmd;
1843 PVE
::Tools
::run_command
($cmd);
1847 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1849 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1851 my $sock = IO
::Socket
::IP-
>new(
1856 GetAddrInfoFlags
=> 0,
1857 ) or die "failed to create socket: $!\n";
1858 # Inside the worker we shouldn't have any previous alarms
1859 # running anyway...:
1861 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1863 accept(my $cli, $sock) or die "connection failed: $!\n";
1866 if (PVE
::Tools
::run_command
($cmd,
1867 output
=> '>&'.fileno($cli),
1868 input
=> '<&'.fileno($cli),
1871 die "Failed to run vncproxy.\n";
1878 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1880 PVE
::Tools
::wait_for_vnc_port
($port);
1889 $res->{password
} = $password if $param->{'generate-password'};
1894 __PACKAGE__-
>register_method({
1895 name
=> 'termproxy',
1896 path
=> '{vmid}/termproxy',
1900 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1902 description
=> "Creates a TCP proxy connections.",
1904 additionalProperties
=> 0,
1906 node
=> get_standard_option
('pve-node'),
1907 vmid
=> get_standard_option
('pve-vmid'),
1911 enum
=> [qw(serial0 serial1 serial2 serial3)],
1912 description
=> "opens a serial terminal (defaults to display)",
1917 additionalProperties
=> 0,
1919 user
=> { type
=> 'string' },
1920 ticket
=> { type
=> 'string' },
1921 port
=> { type
=> 'integer' },
1922 upid
=> { type
=> 'string' },
1928 my $rpcenv = PVE
::RPCEnvironment
::get
();
1930 my $authuser = $rpcenv->get_user();
1932 my $vmid = $param->{vmid
};
1933 my $node = $param->{node
};
1934 my $serial = $param->{serial
};
1936 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1938 if (!defined($serial)) {
1940 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1941 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1945 my $authpath = "/vms/$vmid";
1947 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1952 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1953 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1954 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1955 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1956 push @$remcmd, '--';
1958 $family = PVE
::Tools
::get_host_address_family
($node);
1961 my $port = PVE
::Tools
::next_vnc_port
($family);
1963 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1964 push @$termcmd, '-iface', $serial if $serial;
1969 syslog
('info', "starting qemu termproxy $upid\n");
1971 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1972 '--perm', 'VM.Console', '--'];
1973 push @$cmd, @$remcmd, @$termcmd;
1975 PVE
::Tools
::run_command
($cmd);
1978 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1980 PVE
::Tools
::wait_for_vnc_port
($port);
1990 __PACKAGE__-
>register_method({
1991 name
=> 'vncwebsocket',
1992 path
=> '{vmid}/vncwebsocket',
1995 description
=> "You also need to pass a valid ticket (vncticket).",
1996 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1998 description
=> "Opens a weksocket for VNC traffic.",
2000 additionalProperties
=> 0,
2002 node
=> get_standard_option
('pve-node'),
2003 vmid
=> get_standard_option
('pve-vmid'),
2005 description
=> "Ticket from previous call to vncproxy.",
2010 description
=> "Port number returned by previous vncproxy call.",
2020 port
=> { type
=> 'string' },
2026 my $rpcenv = PVE
::RPCEnvironment
::get
();
2028 my $authuser = $rpcenv->get_user();
2030 my $vmid = $param->{vmid
};
2031 my $node = $param->{node
};
2033 my $authpath = "/vms/$vmid";
2035 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2037 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2039 # Note: VNC ports are acessible from outside, so we do not gain any
2040 # security if we verify that $param->{port} belongs to VM $vmid. This
2041 # check is done by verifying the VNC ticket (inside VNC protocol).
2043 my $port = $param->{port
};
2045 return { port
=> $port };
2048 __PACKAGE__-
>register_method({
2049 name
=> 'spiceproxy',
2050 path
=> '{vmid}/spiceproxy',
2055 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2057 description
=> "Returns a SPICE configuration to connect to the VM.",
2059 additionalProperties
=> 0,
2061 node
=> get_standard_option
('pve-node'),
2062 vmid
=> get_standard_option
('pve-vmid'),
2063 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2066 returns
=> get_standard_option
('remote-viewer-config'),
2070 my $rpcenv = PVE
::RPCEnvironment
::get
();
2072 my $authuser = $rpcenv->get_user();
2074 my $vmid = $param->{vmid
};
2075 my $node = $param->{node
};
2076 my $proxy = $param->{proxy
};
2078 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2079 my $title = "VM $vmid";
2080 $title .= " - ". $conf->{name
} if $conf->{name
};
2082 my $port = PVE
::QemuServer
::spice_port
($vmid);
2084 my ($ticket, undef, $remote_viewer_config) =
2085 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2087 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2088 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2090 return $remote_viewer_config;
2093 __PACKAGE__-
>register_method({
2095 path
=> '{vmid}/status',
2098 description
=> "Directory index",
2103 additionalProperties
=> 0,
2105 node
=> get_standard_option
('pve-node'),
2106 vmid
=> get_standard_option
('pve-vmid'),
2114 subdir
=> { type
=> 'string' },
2117 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2123 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2126 { subdir
=> 'current' },
2127 { subdir
=> 'start' },
2128 { subdir
=> 'stop' },
2129 { subdir
=> 'reset' },
2130 { subdir
=> 'shutdown' },
2131 { subdir
=> 'suspend' },
2132 { subdir
=> 'reboot' },
2138 __PACKAGE__-
>register_method({
2139 name
=> 'vm_status',
2140 path
=> '{vmid}/status/current',
2143 protected
=> 1, # qemu pid files are only readable by root
2144 description
=> "Get virtual machine status.",
2146 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2149 additionalProperties
=> 0,
2151 node
=> get_standard_option
('pve-node'),
2152 vmid
=> get_standard_option
('pve-vmid'),
2158 %$PVE::QemuServer
::vmstatus_return_properties
,
2160 description
=> "HA manager service status.",
2164 description
=> "Qemu VGA configuration supports spice.",
2169 description
=> "Qemu GuestAgent enabled in config.",
2179 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2181 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2182 my $status = $vmstatus->{$param->{vmid
}};
2184 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2186 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2187 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2192 __PACKAGE__-
>register_method({
2194 path
=> '{vmid}/status/start',
2198 description
=> "Start virtual machine.",
2200 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2203 additionalProperties
=> 0,
2205 node
=> get_standard_option
('pve-node'),
2206 vmid
=> get_standard_option
('pve-vmid',
2207 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2208 skiplock
=> get_standard_option
('skiplock'),
2209 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2210 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2213 enum
=> ['secure', 'insecure'],
2214 description
=> "Migration traffic is encrypted using an SSH " .
2215 "tunnel by default. On secure, completely private networks " .
2216 "this can be disabled to increase performance.",
2219 migration_network
=> {
2220 type
=> 'string', format
=> 'CIDR',
2221 description
=> "CIDR of the (sub) network that is used for migration.",
2224 machine
=> get_standard_option
('pve-qemu-machine'),
2226 description
=> "Override QEMU's -cpu argument with the given string.",
2230 targetstorage
=> get_standard_option
('pve-targetstorage'),
2232 description
=> "Wait maximal timeout seconds.",
2235 default => 'max(30, vm memory in GiB)',
2246 my $rpcenv = PVE
::RPCEnvironment
::get
();
2247 my $authuser = $rpcenv->get_user();
2249 my $node = extract_param
($param, 'node');
2250 my $vmid = extract_param
($param, 'vmid');
2251 my $timeout = extract_param
($param, 'timeout');
2253 my $machine = extract_param
($param, 'machine');
2254 my $force_cpu = extract_param
($param, 'force-cpu');
2256 my $get_root_param = sub {
2257 my $value = extract_param
($param, $_[0]);
2258 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2259 if $value && $authuser ne 'root@pam';
2263 my $stateuri = $get_root_param->('stateuri');
2264 my $skiplock = $get_root_param->('skiplock');
2265 my $migratedfrom = $get_root_param->('migratedfrom');
2266 my $migration_type = $get_root_param->('migration_type');
2267 my $migration_network = $get_root_param->('migration_network');
2268 my $targetstorage = $get_root_param->('targetstorage');
2272 if ($targetstorage) {
2273 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2275 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2276 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2280 # read spice ticket from STDIN
2282 my $nbd_protocol_version = 0;
2283 my $replicated_volumes = {};
2284 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2285 while (defined(my $line = <STDIN
>)) {
2287 if ($line =~ m/^spice_ticket: (.+)$/) {
2289 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2290 $nbd_protocol_version = $1;
2291 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2292 $replicated_volumes->{$1} = 1;
2294 # fallback for old source node
2295 $spice_ticket = $line;
2300 PVE
::Cluster
::check_cfs_quorum
();
2302 my $storecfg = PVE
::Storage
::config
();
2304 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2308 print "Requesting HA start for VM $vmid\n";
2310 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2311 PVE
::Tools
::run_command
($cmd);
2315 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2322 syslog
('info', "start VM $vmid: $upid\n");
2324 my $migrate_opts = {
2325 migratedfrom
=> $migratedfrom,
2326 spice_ticket
=> $spice_ticket,
2327 network
=> $migration_network,
2328 type
=> $migration_type,
2329 storagemap
=> $storagemap,
2330 nbd_proto_version
=> $nbd_protocol_version,
2331 replicated_volumes
=> $replicated_volumes,
2335 statefile
=> $stateuri,
2336 skiplock
=> $skiplock,
2337 forcemachine
=> $machine,
2338 timeout
=> $timeout,
2339 forcecpu
=> $force_cpu,
2342 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2346 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2350 __PACKAGE__-
>register_method({
2352 path
=> '{vmid}/status/stop',
2356 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2357 "is akin to pulling the power plug of a running computer and may damage the VM data",
2359 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2362 additionalProperties
=> 0,
2364 node
=> get_standard_option
('pve-node'),
2365 vmid
=> get_standard_option
('pve-vmid',
2366 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2367 skiplock
=> get_standard_option
('skiplock'),
2368 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2370 description
=> "Wait maximal timeout seconds.",
2376 description
=> "Do not deactivate storage volumes.",
2389 my $rpcenv = PVE
::RPCEnvironment
::get
();
2390 my $authuser = $rpcenv->get_user();
2392 my $node = extract_param
($param, 'node');
2393 my $vmid = extract_param
($param, 'vmid');
2395 my $skiplock = extract_param
($param, 'skiplock');
2396 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2397 if $skiplock && $authuser ne 'root@pam';
2399 my $keepActive = extract_param
($param, 'keepActive');
2400 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2401 if $keepActive && $authuser ne 'root@pam';
2403 my $migratedfrom = extract_param
($param, 'migratedfrom');
2404 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2405 if $migratedfrom && $authuser ne 'root@pam';
2408 my $storecfg = PVE
::Storage
::config
();
2410 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2415 print "Requesting HA stop for VM $vmid\n";
2417 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2418 PVE
::Tools
::run_command
($cmd);
2422 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2428 syslog
('info', "stop VM $vmid: $upid\n");
2430 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2431 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2435 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2439 __PACKAGE__-
>register_method({
2441 path
=> '{vmid}/status/reset',
2445 description
=> "Reset virtual machine.",
2447 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2450 additionalProperties
=> 0,
2452 node
=> get_standard_option
('pve-node'),
2453 vmid
=> get_standard_option
('pve-vmid',
2454 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2455 skiplock
=> get_standard_option
('skiplock'),
2464 my $rpcenv = PVE
::RPCEnvironment
::get
();
2466 my $authuser = $rpcenv->get_user();
2468 my $node = extract_param
($param, 'node');
2470 my $vmid = extract_param
($param, 'vmid');
2472 my $skiplock = extract_param
($param, 'skiplock');
2473 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2474 if $skiplock && $authuser ne 'root@pam';
2476 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2481 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2486 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2489 __PACKAGE__-
>register_method({
2490 name
=> 'vm_shutdown',
2491 path
=> '{vmid}/status/shutdown',
2495 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2496 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2498 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2501 additionalProperties
=> 0,
2503 node
=> get_standard_option
('pve-node'),
2504 vmid
=> get_standard_option
('pve-vmid',
2505 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2506 skiplock
=> get_standard_option
('skiplock'),
2508 description
=> "Wait maximal timeout seconds.",
2514 description
=> "Make sure the VM stops.",
2520 description
=> "Do not deactivate storage volumes.",
2533 my $rpcenv = PVE
::RPCEnvironment
::get
();
2534 my $authuser = $rpcenv->get_user();
2536 my $node = extract_param
($param, 'node');
2537 my $vmid = extract_param
($param, 'vmid');
2539 my $skiplock = extract_param
($param, 'skiplock');
2540 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2541 if $skiplock && $authuser ne 'root@pam';
2543 my $keepActive = extract_param
($param, 'keepActive');
2544 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2545 if $keepActive && $authuser ne 'root@pam';
2547 my $storecfg = PVE
::Storage
::config
();
2551 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2552 # otherwise, we will infer a shutdown command, but run into the timeout,
2553 # then when the vm is resumed, it will instantly shutdown
2555 # checking the qmp status here to get feedback to the gui/cli/api
2556 # and the status query should not take too long
2557 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2558 if ($param->{forceStop
}) {
2559 warn "VM is paused - stop instead of shutdown\n";
2562 die "VM is paused - cannot shutdown\n";
2566 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2568 my $timeout = $param->{timeout
} // 60;
2572 print "Requesting HA stop for VM $vmid\n";
2574 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2575 PVE
::Tools
::run_command
($cmd);
2579 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2586 syslog
('info', "shutdown VM $vmid: $upid\n");
2588 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2589 $shutdown, $param->{forceStop
}, $keepActive);
2593 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2597 __PACKAGE__-
>register_method({
2598 name
=> 'vm_reboot',
2599 path
=> '{vmid}/status/reboot',
2603 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2605 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2608 additionalProperties
=> 0,
2610 node
=> get_standard_option
('pve-node'),
2611 vmid
=> get_standard_option
('pve-vmid',
2612 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2614 description
=> "Wait maximal timeout seconds for the shutdown.",
2627 my $rpcenv = PVE
::RPCEnvironment
::get
();
2628 my $authuser = $rpcenv->get_user();
2630 my $node = extract_param
($param, 'node');
2631 my $vmid = extract_param
($param, 'vmid');
2633 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2635 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2640 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2641 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2645 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2648 __PACKAGE__-
>register_method({
2649 name
=> 'vm_suspend',
2650 path
=> '{vmid}/status/suspend',
2654 description
=> "Suspend virtual machine.",
2656 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2657 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2658 " on the storage for the vmstate.",
2659 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2662 additionalProperties
=> 0,
2664 node
=> get_standard_option
('pve-node'),
2665 vmid
=> get_standard_option
('pve-vmid',
2666 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2667 skiplock
=> get_standard_option
('skiplock'),
2672 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2674 statestorage
=> get_standard_option
('pve-storage-id', {
2675 description
=> "The storage for the VM state",
2676 requires
=> 'todisk',
2678 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2688 my $rpcenv = PVE
::RPCEnvironment
::get
();
2689 my $authuser = $rpcenv->get_user();
2691 my $node = extract_param
($param, 'node');
2692 my $vmid = extract_param
($param, 'vmid');
2694 my $todisk = extract_param
($param, 'todisk') // 0;
2696 my $statestorage = extract_param
($param, 'statestorage');
2698 my $skiplock = extract_param
($param, 'skiplock');
2699 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2700 if $skiplock && $authuser ne 'root@pam';
2702 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2704 die "Cannot suspend HA managed VM to disk\n"
2705 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2707 # early check for storage permission, for better user feedback
2709 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2711 if (!$statestorage) {
2712 # get statestorage from config if none is given
2713 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2714 my $storecfg = PVE
::Storage
::config
();
2715 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2718 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2724 syslog
('info', "suspend VM $vmid: $upid\n");
2726 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2731 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2732 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2735 __PACKAGE__-
>register_method({
2736 name
=> 'vm_resume',
2737 path
=> '{vmid}/status/resume',
2741 description
=> "Resume virtual machine.",
2743 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2746 additionalProperties
=> 0,
2748 node
=> get_standard_option
('pve-node'),
2749 vmid
=> get_standard_option
('pve-vmid',
2750 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2751 skiplock
=> get_standard_option
('skiplock'),
2752 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2762 my $rpcenv = PVE
::RPCEnvironment
::get
();
2764 my $authuser = $rpcenv->get_user();
2766 my $node = extract_param
($param, 'node');
2768 my $vmid = extract_param
($param, 'vmid');
2770 my $skiplock = extract_param
($param, 'skiplock');
2771 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2772 if $skiplock && $authuser ne 'root@pam';
2774 my $nocheck = extract_param
($param, 'nocheck');
2775 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2776 if $nocheck && $authuser ne 'root@pam';
2778 my $to_disk_suspended;
2780 PVE
::QemuConfig-
>lock_config($vmid, sub {
2781 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2782 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2786 die "VM $vmid not running\n"
2787 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2792 syslog
('info', "resume VM $vmid: $upid\n");
2794 if (!$to_disk_suspended) {
2795 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2797 my $storecfg = PVE
::Storage
::config
();
2798 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2804 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2807 __PACKAGE__-
>register_method({
2808 name
=> 'vm_sendkey',
2809 path
=> '{vmid}/sendkey',
2813 description
=> "Send key event to virtual machine.",
2815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2818 additionalProperties
=> 0,
2820 node
=> get_standard_option
('pve-node'),
2821 vmid
=> get_standard_option
('pve-vmid',
2822 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2823 skiplock
=> get_standard_option
('skiplock'),
2825 description
=> "The key (qemu monitor encoding).",
2830 returns
=> { type
=> 'null'},
2834 my $rpcenv = PVE
::RPCEnvironment
::get
();
2836 my $authuser = $rpcenv->get_user();
2838 my $node = extract_param
($param, 'node');
2840 my $vmid = extract_param
($param, 'vmid');
2842 my $skiplock = extract_param
($param, 'skiplock');
2843 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2844 if $skiplock && $authuser ne 'root@pam';
2846 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2851 __PACKAGE__-
>register_method({
2852 name
=> 'vm_feature',
2853 path
=> '{vmid}/feature',
2857 description
=> "Check if feature for virtual machine is available.",
2859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2862 additionalProperties
=> 0,
2864 node
=> get_standard_option
('pve-node'),
2865 vmid
=> get_standard_option
('pve-vmid'),
2867 description
=> "Feature to check.",
2869 enum
=> [ 'snapshot', 'clone', 'copy' ],
2871 snapname
=> get_standard_option
('pve-snapshot-name', {
2879 hasFeature
=> { type
=> 'boolean' },
2882 items
=> { type
=> 'string' },
2889 my $node = extract_param
($param, 'node');
2891 my $vmid = extract_param
($param, 'vmid');
2893 my $snapname = extract_param
($param, 'snapname');
2895 my $feature = extract_param
($param, 'feature');
2897 my $running = PVE
::QemuServer
::check_running
($vmid);
2899 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2902 my $snap = $conf->{snapshots
}->{$snapname};
2903 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2906 my $storecfg = PVE
::Storage
::config
();
2908 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2909 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2912 hasFeature
=> $hasFeature,
2913 nodes
=> [ keys %$nodelist ],
2917 __PACKAGE__-
>register_method({
2919 path
=> '{vmid}/clone',
2923 description
=> "Create a copy of virtual machine/template.",
2925 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2926 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2927 "'Datastore.AllocateSpace' on any used storage.",
2930 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2932 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2933 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2938 additionalProperties
=> 0,
2940 node
=> get_standard_option
('pve-node'),
2941 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2942 newid
=> get_standard_option
('pve-vmid', {
2943 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2944 description
=> 'VMID for the clone.' }),
2947 type
=> 'string', format
=> 'dns-name',
2948 description
=> "Set a name for the new VM.",
2953 description
=> "Description for the new VM.",
2957 type
=> 'string', format
=> 'pve-poolid',
2958 description
=> "Add the new VM to the specified pool.",
2960 snapname
=> get_standard_option
('pve-snapshot-name', {
2963 storage
=> get_standard_option
('pve-storage-id', {
2964 description
=> "Target storage for full clone.",
2968 description
=> "Target format for file storage. Only valid for full clone.",
2971 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2976 description
=> "Create a full copy of all disks. This is always done when " .
2977 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2979 target
=> get_standard_option
('pve-node', {
2980 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2984 description
=> "Override I/O bandwidth limit (in KiB/s).",
2988 default => 'clone limit from datacenter or storage config',
2998 my $rpcenv = PVE
::RPCEnvironment
::get
();
2999 my $authuser = $rpcenv->get_user();
3001 my $node = extract_param
($param, 'node');
3002 my $vmid = extract_param
($param, 'vmid');
3003 my $newid = extract_param
($param, 'newid');
3004 my $pool = extract_param
($param, 'pool');
3005 $rpcenv->check_pool_exist($pool) if defined($pool);
3007 my $snapname = extract_param
($param, 'snapname');
3008 my $storage = extract_param
($param, 'storage');
3009 my $format = extract_param
($param, 'format');
3010 my $target = extract_param
($param, 'target');
3012 my $localnode = PVE
::INotify
::nodename
();
3014 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3018 PVE
::Cluster
::check_node_exists
($target) if $target;
3020 my $storecfg = PVE
::Storage
::config
();
3023 # check if storage is enabled on local node
3024 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3026 # check if storage is available on target node
3027 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3028 # clone only works if target storage is shared
3029 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3030 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3034 PVE
::Cluster
::check_cfs_quorum
();
3036 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3039 # do all tests after lock but before forking worker - if possible
3041 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3042 PVE
::QemuConfig-
>check_lock($conf);
3044 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3045 die "unexpected state change\n" if $verify_running != $running;
3047 die "snapshot '$snapname' does not exist\n"
3048 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3050 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3052 die "parameter 'storage' not allowed for linked clones\n"
3053 if defined($storage) && !$full;
3055 die "parameter 'format' not allowed for linked clones\n"
3056 if defined($format) && !$full;
3058 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3060 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3062 die "can't clone VM to node '$target' (VM uses local storage)\n"
3063 if $target && !$sharedvm;
3065 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3066 die "unable to create VM $newid: config file already exists\n"
3069 my $newconf = { lock => 'clone' };
3074 foreach my $opt (keys %$oldconf) {
3075 my $value = $oldconf->{$opt};
3077 # do not copy snapshot related info
3078 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3079 $opt eq 'vmstate' || $opt eq 'snapstate';
3081 # no need to copy unused images, because VMID(owner) changes anyways
3082 next if $opt =~ m/^unused\d+$/;
3084 # always change MAC! address
3085 if ($opt =~ m/^net(\d+)$/) {
3086 my $net = PVE
::QemuServer
::parse_net
($value);
3087 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3088 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3089 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3090 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3091 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3092 die "unable to parse drive options for '$opt'\n" if !$drive;
3093 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3094 $newconf->{$opt} = $value; # simply copy configuration
3096 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3097 die "Full clone feature is not supported for drive '$opt'\n"
3098 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3099 $fullclone->{$opt} = 1;
3101 # not full means clone instead of copy
3102 die "Linked clone feature is not supported for drive '$opt'\n"
3103 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3105 $drives->{$opt} = $drive;
3106 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3107 push @$vollist, $drive->{file
};
3110 # copy everything else
3111 $newconf->{$opt} = $value;
3115 # auto generate a new uuid
3116 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3117 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3118 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3119 # auto generate a new vmgenid only if the option was set for template
3120 if ($newconf->{vmgenid
}) {
3121 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3124 delete $newconf->{template
};
3126 if ($param->{name
}) {
3127 $newconf->{name
} = $param->{name
};
3129 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3132 if ($param->{description
}) {
3133 $newconf->{description
} = $param->{description
};
3136 # create empty/temp config - this fails if VM already exists on other node
3137 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3138 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3143 my $newvollist = [];
3150 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3152 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3154 my $bwlimit = extract_param
($param, 'bwlimit');
3156 my $total_jobs = scalar(keys %{$drives});
3159 foreach my $opt (sort keys %$drives) {
3160 my $drive = $drives->{$opt};
3161 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3162 my $completion = $skipcomplete ?
'skip' : 'complete';
3164 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3165 my $storage_list = [ $src_sid ];
3166 push @$storage_list, $storage if defined($storage);
3167 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3169 my $newdrive = PVE
::QemuServer
::clone_disk
(
3188 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3190 PVE
::QemuConfig-
>write_config($newid, $newconf);
3194 delete $newconf->{lock};
3196 # do not write pending changes
3197 if (my @changes = keys %{$newconf->{pending
}}) {
3198 my $pending = join(',', @changes);
3199 warn "found pending changes for '$pending', discarding for clone\n";
3200 delete $newconf->{pending
};
3203 PVE
::QemuConfig-
>write_config($newid, $newconf);
3206 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3207 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3208 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3210 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3211 die "Failed to move config to node '$target' - rename failed: $!\n"
3212 if !rename($conffile, $newconffile);
3215 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3218 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3219 sleep 1; # some storage like rbd need to wait before release volume - really?
3221 foreach my $volid (@$newvollist) {
3222 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3226 PVE
::Firewall
::remove_vmfw_conf
($newid);
3228 unlink $conffile; # avoid races -> last thing before die
3230 die "clone failed: $err";
3236 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3238 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3241 # Aquire exclusive lock lock for $newid
3242 my $lock_target_vm = sub {
3243 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3246 # exclusive lock if VM is running - else shared lock is enough;
3248 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3250 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3254 __PACKAGE__-
>register_method({
3255 name
=> 'move_vm_disk',
3256 path
=> '{vmid}/move_disk',
3260 description
=> "Move volume to different storage.",
3262 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3264 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3265 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3269 additionalProperties
=> 0,
3271 node
=> get_standard_option
('pve-node'),
3272 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3275 description
=> "The disk you want to move.",
3276 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3278 storage
=> get_standard_option
('pve-storage-id', {
3279 description
=> "Target storage.",
3280 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3284 description
=> "Target Format.",
3285 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3290 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3296 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3301 description
=> "Override I/O bandwidth limit (in KiB/s).",
3305 default => 'move limit from datacenter or storage config',
3311 description
=> "the task ID.",
3316 my $rpcenv = PVE
::RPCEnvironment
::get
();
3317 my $authuser = $rpcenv->get_user();
3319 my $node = extract_param
($param, 'node');
3320 my $vmid = extract_param
($param, 'vmid');
3321 my $digest = extract_param
($param, 'digest');
3322 my $disk = extract_param
($param, 'disk');
3323 my $storeid = extract_param
($param, 'storage');
3324 my $format = extract_param
($param, 'format');
3326 my $storecfg = PVE
::Storage
::config
();
3328 my $updatefn = sub {
3329 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3330 PVE
::QemuConfig-
>check_lock($conf);
3332 die "VM config checksum missmatch (file change by other user?)\n"
3333 if $digest && $digest ne $conf->{digest
};
3335 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3337 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3339 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3340 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3342 my $old_volid = $drive->{file
};
3344 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3345 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3349 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3350 (!$format || !$oldfmt || $oldfmt eq $format);
3352 # this only checks snapshots because $disk is passed!
3353 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3354 die "you can't move a disk with snapshots and delete the source\n"
3355 if $snapshotted && $param->{delete};
3357 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3359 my $running = PVE
::QemuServer
::check_running
($vmid);
3361 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3364 my $newvollist = [];
3370 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3372 warn "moving disk with snapshots, snapshots will not be moved!\n"
3375 my $bwlimit = extract_param
($param, 'bwlimit');
3376 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3378 my $newdrive = PVE
::QemuServer
::clone_disk
(
3396 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3398 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3400 # convert moved disk to base if part of template
3401 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3402 if PVE
::QemuConfig-
>is_template($conf);
3404 PVE
::QemuConfig-
>write_config($vmid, $conf);
3406 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3407 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3408 eval { mon_cmd
($vmid, "guest-fstrim") };
3412 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3413 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3419 foreach my $volid (@$newvollist) {
3420 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3423 die "storage migration failed: $err";
3426 if ($param->{delete}) {
3428 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3429 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3435 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3438 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3441 my $check_vm_disks_local = sub {
3442 my ($storecfg, $vmconf, $vmid) = @_;
3444 my $local_disks = {};
3446 # add some more information to the disks e.g. cdrom
3447 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3448 my ($volid, $attr) = @_;
3450 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3452 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3453 return if $scfg->{shared
};
3455 # The shared attr here is just a special case where the vdisk
3456 # is marked as shared manually
3457 return if $attr->{shared
};
3458 return if $attr->{cdrom
} and $volid eq "none";
3460 if (exists $local_disks->{$volid}) {
3461 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3463 $local_disks->{$volid} = $attr;
3464 # ensure volid is present in case it's needed
3465 $local_disks->{$volid}->{volid
} = $volid;
3469 return $local_disks;
3472 __PACKAGE__-
>register_method({
3473 name
=> 'migrate_vm_precondition',
3474 path
=> '{vmid}/migrate',
3478 description
=> "Get preconditions for migration.",
3480 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3483 additionalProperties
=> 0,
3485 node
=> get_standard_option
('pve-node'),
3486 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3487 target
=> get_standard_option
('pve-node', {
3488 description
=> "Target node.",
3489 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3497 running
=> { type
=> 'boolean' },
3501 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3503 not_allowed_nodes
=> {
3506 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3510 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3512 local_resources
=> {
3514 description
=> "List local resources e.g. pci, usb"
3521 my $rpcenv = PVE
::RPCEnvironment
::get
();
3523 my $authuser = $rpcenv->get_user();
3525 PVE
::Cluster
::check_cfs_quorum
();
3529 my $vmid = extract_param
($param, 'vmid');
3530 my $target = extract_param
($param, 'target');
3531 my $localnode = PVE
::INotify
::nodename
();
3535 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3536 my $storecfg = PVE
::Storage
::config
();
3539 # try to detect errors early
3540 PVE
::QemuConfig-
>check_lock($vmconf);
3542 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3544 # if vm is not running, return target nodes where local storage is available
3545 # for offline migration
3546 if (!$res->{running
}) {
3547 $res->{allowed_nodes
} = [];
3548 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3549 delete $checked_nodes->{$localnode};
3551 foreach my $node (keys %$checked_nodes) {
3552 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3553 push @{$res->{allowed_nodes
}}, $node;
3557 $res->{not_allowed_nodes
} = $checked_nodes;
3561 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3562 $res->{local_disks
} = [ values %$local_disks ];;
3564 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3566 $res->{local_resources
} = $local_resources;
3573 __PACKAGE__-
>register_method({
3574 name
=> 'migrate_vm',
3575 path
=> '{vmid}/migrate',
3579 description
=> "Migrate virtual machine. Creates a new migration task.",
3581 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3584 additionalProperties
=> 0,
3586 node
=> get_standard_option
('pve-node'),
3587 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3588 target
=> get_standard_option
('pve-node', {
3589 description
=> "Target node.",
3590 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3594 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3599 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3604 enum
=> ['secure', 'insecure'],
3605 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3608 migration_network
=> {
3609 type
=> 'string', format
=> 'CIDR',
3610 description
=> "CIDR of the (sub) network that is used for migration.",
3613 "with-local-disks" => {
3615 description
=> "Enable live storage migration for local disk",
3618 targetstorage
=> get_standard_option
('pve-targetstorage', {
3619 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3622 description
=> "Override I/O bandwidth limit (in KiB/s).",
3626 default => 'migrate limit from datacenter or storage config',
3632 description
=> "the task ID.",
3637 my $rpcenv = PVE
::RPCEnvironment
::get
();
3638 my $authuser = $rpcenv->get_user();
3640 my $target = extract_param
($param, 'target');
3642 my $localnode = PVE
::INotify
::nodename
();
3643 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3645 PVE
::Cluster
::check_cfs_quorum
();
3647 PVE
::Cluster
::check_node_exists
($target);
3649 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3651 my $vmid = extract_param
($param, 'vmid');
3653 raise_param_exc
({ force
=> "Only root may use this option." })
3654 if $param->{force
} && $authuser ne 'root@pam';
3656 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3657 if $param->{migration_type
} && $authuser ne 'root@pam';
3659 # allow root only until better network permissions are available
3660 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3661 if $param->{migration_network
} && $authuser ne 'root@pam';
3664 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3666 # try to detect errors early
3668 PVE
::QemuConfig-
>check_lock($conf);
3670 if (PVE
::QemuServer
::check_running
($vmid)) {
3671 die "can't migrate running VM without --online\n" if !$param->{online
};
3673 my $repl_conf = PVE
::ReplicationConfig-
>new();
3674 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3675 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3676 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3677 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3678 "target. Use 'force' to override.\n";
3681 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3682 $param->{online
} = 0;
3685 my $storecfg = PVE
::Storage
::config
();
3687 if (my $targetstorage = $param->{targetstorage
}) {
3688 my $check_storage = sub {
3689 my ($target_sid) = @_;
3690 PVE
::Storage
::storage_check_enabled
($storecfg, $target_sid, $target);
3691 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3692 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3693 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3694 if !$scfg->{content
}->{images
};
3697 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3698 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3701 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3702 if !defined($storagemap->{identity
});
3704 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3705 $check_storage->($target_sid);
3708 $check_storage->($storagemap->{default})
3709 if $storagemap->{default};
3711 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3712 if $storagemap->{identity
};
3714 $param->{storagemap
} = $storagemap;
3716 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3719 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3724 print "Requesting HA migration for VM $vmid to node $target\n";
3726 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3727 PVE
::Tools
::run_command
($cmd);
3731 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3736 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3740 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3743 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3748 __PACKAGE__-
>register_method({
3750 path
=> '{vmid}/monitor',
3754 description
=> "Execute Qemu monitor commands.",
3756 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3757 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3760 additionalProperties
=> 0,
3762 node
=> get_standard_option
('pve-node'),
3763 vmid
=> get_standard_option
('pve-vmid'),
3766 description
=> "The monitor command.",
3770 returns
=> { type
=> 'string'},
3774 my $rpcenv = PVE
::RPCEnvironment
::get
();
3775 my $authuser = $rpcenv->get_user();
3778 my $command = shift;
3779 return $command =~ m/^\s*info(\s+|$)/
3780 || $command =~ m/^\s*help\s*$/;
3783 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3784 if !&$is_ro($param->{command
});
3786 my $vmid = $param->{vmid
};
3788 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3792 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3794 $res = "ERROR: $@" if $@;
3799 __PACKAGE__-
>register_method({
3800 name
=> 'resize_vm',
3801 path
=> '{vmid}/resize',
3805 description
=> "Extend volume size.",
3807 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3810 additionalProperties
=> 0,
3812 node
=> get_standard_option
('pve-node'),
3813 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3814 skiplock
=> get_standard_option
('skiplock'),
3817 description
=> "The disk you want to resize.",
3818 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3822 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3823 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.",
3827 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3833 returns
=> { type
=> 'null'},
3837 my $rpcenv = PVE
::RPCEnvironment
::get
();
3839 my $authuser = $rpcenv->get_user();
3841 my $node = extract_param
($param, 'node');
3843 my $vmid = extract_param
($param, 'vmid');
3845 my $digest = extract_param
($param, 'digest');
3847 my $disk = extract_param
($param, 'disk');
3849 my $sizestr = extract_param
($param, 'size');
3851 my $skiplock = extract_param
($param, 'skiplock');
3852 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3853 if $skiplock && $authuser ne 'root@pam';
3855 my $storecfg = PVE
::Storage
::config
();
3857 my $updatefn = sub {
3859 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3861 die "checksum missmatch (file change by other user?)\n"
3862 if $digest && $digest ne $conf->{digest
};
3863 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3865 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3867 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3869 my (undef, undef, undef, undef, undef, undef, $format) =
3870 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3872 die "can't resize volume: $disk if snapshot exists\n"
3873 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3875 my $volid = $drive->{file
};
3877 die "disk '$disk' has no associated volume\n" if !$volid;
3879 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3881 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3883 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3885 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3886 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3888 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3890 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3891 my ($ext, $newsize, $unit) = ($1, $2, $4);
3894 $newsize = $newsize * 1024;
3895 } elsif ($unit eq 'M') {
3896 $newsize = $newsize * 1024 * 1024;
3897 } elsif ($unit eq 'G') {
3898 $newsize = $newsize * 1024 * 1024 * 1024;
3899 } elsif ($unit eq 'T') {
3900 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3903 $newsize += $size if $ext;
3904 $newsize = int($newsize);
3906 die "shrinking disks is not supported\n" if $newsize < $size;
3908 return if $size == $newsize;
3910 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3912 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3914 $drive->{size
} = $newsize;
3915 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3917 PVE
::QemuConfig-
>write_config($vmid, $conf);
3920 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3924 __PACKAGE__-
>register_method({
3925 name
=> 'snapshot_list',
3926 path
=> '{vmid}/snapshot',
3928 description
=> "List all snapshots.",
3930 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3933 protected
=> 1, # qemu pid files are only readable by root
3935 additionalProperties
=> 0,
3937 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3938 node
=> get_standard_option
('pve-node'),
3947 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3951 description
=> "Snapshot includes RAM.",
3956 description
=> "Snapshot description.",
3960 description
=> "Snapshot creation time",
3962 renderer
=> 'timestamp',
3966 description
=> "Parent snapshot identifier.",
3972 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3977 my $vmid = $param->{vmid
};
3979 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3980 my $snaphash = $conf->{snapshots
} || {};
3984 foreach my $name (keys %$snaphash) {
3985 my $d = $snaphash->{$name};
3988 snaptime
=> $d->{snaptime
} || 0,
3989 vmstate
=> $d->{vmstate
} ?
1 : 0,
3990 description
=> $d->{description
} || '',
3992 $item->{parent
} = $d->{parent
} if $d->{parent
};
3993 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3997 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4000 digest
=> $conf->{digest
},
4001 running
=> $running,
4002 description
=> "You are here!",
4004 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4006 push @$res, $current;
4011 __PACKAGE__-
>register_method({
4013 path
=> '{vmid}/snapshot',
4017 description
=> "Snapshot a VM.",
4019 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4022 additionalProperties
=> 0,
4024 node
=> get_standard_option
('pve-node'),
4025 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4026 snapname
=> get_standard_option
('pve-snapshot-name'),
4030 description
=> "Save the vmstate",
4035 description
=> "A textual description or comment.",
4041 description
=> "the task ID.",
4046 my $rpcenv = PVE
::RPCEnvironment
::get
();
4048 my $authuser = $rpcenv->get_user();
4050 my $node = extract_param
($param, 'node');
4052 my $vmid = extract_param
($param, 'vmid');
4054 my $snapname = extract_param
($param, 'snapname');
4056 die "unable to use snapshot name 'current' (reserved name)\n"
4057 if $snapname eq 'current';
4059 die "unable to use snapshot name 'pending' (reserved name)\n"
4060 if lc($snapname) eq 'pending';
4063 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4064 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4065 $param->{description
});
4068 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4071 __PACKAGE__-
>register_method({
4072 name
=> 'snapshot_cmd_idx',
4073 path
=> '{vmid}/snapshot/{snapname}',
4080 additionalProperties
=> 0,
4082 vmid
=> get_standard_option
('pve-vmid'),
4083 node
=> get_standard_option
('pve-node'),
4084 snapname
=> get_standard_option
('pve-snapshot-name'),
4093 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4100 push @$res, { cmd
=> 'rollback' };
4101 push @$res, { cmd
=> 'config' };
4106 __PACKAGE__-
>register_method({
4107 name
=> 'update_snapshot_config',
4108 path
=> '{vmid}/snapshot/{snapname}/config',
4112 description
=> "Update snapshot metadata.",
4114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4117 additionalProperties
=> 0,
4119 node
=> get_standard_option
('pve-node'),
4120 vmid
=> get_standard_option
('pve-vmid'),
4121 snapname
=> get_standard_option
('pve-snapshot-name'),
4125 description
=> "A textual description or comment.",
4129 returns
=> { type
=> 'null' },
4133 my $rpcenv = PVE
::RPCEnvironment
::get
();
4135 my $authuser = $rpcenv->get_user();
4137 my $vmid = extract_param
($param, 'vmid');
4139 my $snapname = extract_param
($param, 'snapname');
4141 return if !defined($param->{description
});
4143 my $updatefn = sub {
4145 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4147 PVE
::QemuConfig-
>check_lock($conf);
4149 my $snap = $conf->{snapshots
}->{$snapname};
4151 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4153 $snap->{description
} = $param->{description
} if defined($param->{description
});
4155 PVE
::QemuConfig-
>write_config($vmid, $conf);
4158 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4163 __PACKAGE__-
>register_method({
4164 name
=> 'get_snapshot_config',
4165 path
=> '{vmid}/snapshot/{snapname}/config',
4168 description
=> "Get snapshot configuration",
4170 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4173 additionalProperties
=> 0,
4175 node
=> get_standard_option
('pve-node'),
4176 vmid
=> get_standard_option
('pve-vmid'),
4177 snapname
=> get_standard_option
('pve-snapshot-name'),
4180 returns
=> { type
=> "object" },
4184 my $rpcenv = PVE
::RPCEnvironment
::get
();
4186 my $authuser = $rpcenv->get_user();
4188 my $vmid = extract_param
($param, 'vmid');
4190 my $snapname = extract_param
($param, 'snapname');
4192 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4194 my $snap = $conf->{snapshots
}->{$snapname};
4196 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4201 __PACKAGE__-
>register_method({
4203 path
=> '{vmid}/snapshot/{snapname}/rollback',
4207 description
=> "Rollback VM state to specified snapshot.",
4209 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4212 additionalProperties
=> 0,
4214 node
=> get_standard_option
('pve-node'),
4215 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4216 snapname
=> get_standard_option
('pve-snapshot-name'),
4221 description
=> "the task ID.",
4226 my $rpcenv = PVE
::RPCEnvironment
::get
();
4228 my $authuser = $rpcenv->get_user();
4230 my $node = extract_param
($param, 'node');
4232 my $vmid = extract_param
($param, 'vmid');
4234 my $snapname = extract_param
($param, 'snapname');
4237 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4238 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4242 # hold migration lock, this makes sure that nobody create replication snapshots
4243 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4246 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4249 __PACKAGE__-
>register_method({
4250 name
=> 'delsnapshot',
4251 path
=> '{vmid}/snapshot/{snapname}',
4255 description
=> "Delete a VM snapshot.",
4257 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4260 additionalProperties
=> 0,
4262 node
=> get_standard_option
('pve-node'),
4263 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4264 snapname
=> get_standard_option
('pve-snapshot-name'),
4268 description
=> "For removal from config file, even if removing disk snapshots fails.",
4274 description
=> "the task ID.",
4279 my $rpcenv = PVE
::RPCEnvironment
::get
();
4281 my $authuser = $rpcenv->get_user();
4283 my $node = extract_param
($param, 'node');
4285 my $vmid = extract_param
($param, 'vmid');
4287 my $snapname = extract_param
($param, 'snapname');
4290 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4291 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4294 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4297 __PACKAGE__-
>register_method({
4299 path
=> '{vmid}/template',
4303 description
=> "Create a Template.",
4305 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4306 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4309 additionalProperties
=> 0,
4311 node
=> get_standard_option
('pve-node'),
4312 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4316 description
=> "If you want to convert only 1 disk to base image.",
4317 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4322 returns
=> { type
=> 'null'},
4326 my $rpcenv = PVE
::RPCEnvironment
::get
();
4328 my $authuser = $rpcenv->get_user();
4330 my $node = extract_param
($param, 'node');
4332 my $vmid = extract_param
($param, 'vmid');
4334 my $disk = extract_param
($param, 'disk');
4336 my $updatefn = sub {
4338 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4340 PVE
::QemuConfig-
>check_lock($conf);
4342 die "unable to create template, because VM contains snapshots\n"
4343 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4345 die "you can't convert a template to a template\n"
4346 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4348 die "you can't convert a VM to template if VM is running\n"
4349 if PVE
::QemuServer
::check_running
($vmid);
4352 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4355 $conf->{template
} = 1;
4356 PVE
::QemuConfig-
>write_config($vmid, $conf);
4358 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4361 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4365 __PACKAGE__-
>register_method({
4366 name
=> 'cloudinit_generated_config_dump',
4367 path
=> '{vmid}/cloudinit/dump',
4370 description
=> "Get automatically generated cloudinit config.",
4372 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4375 additionalProperties
=> 0,
4377 node
=> get_standard_option
('pve-node'),
4378 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4380 description
=> 'Config type.',
4382 enum
=> ['user', 'network', 'meta'],
4392 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4394 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});