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)) {
1350 if ($conf->{$opt}) {
1351 $check_drive_perms->($opt, $conf->{$opt});
1355 $check_drive_perms->($opt, $param->{$opt});
1356 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1357 if defined($conf->{pending
}->{$opt});
1359 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1360 } elsif ($opt =~ m/^serial\d+/) {
1361 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1362 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1363 } elsif ($authuser ne 'root@pam') {
1364 die "only root can modify '$opt' config for real devices\n";
1366 $conf->{pending
}->{$opt} = $param->{$opt};
1367 } elsif ($opt =~ m/^usb\d+/) {
1368 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1369 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1370 } elsif ($authuser ne 'root@pam') {
1371 die "only root can modify '$opt' config for real devices\n";
1373 $conf->{pending
}->{$opt} = $param->{$opt};
1375 $conf->{pending
}->{$opt} = $param->{$opt};
1377 if ($opt eq 'boot') {
1378 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1379 if ($new_bootcfg->{order
}) {
1380 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1381 for my $dev (@devs) {
1382 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1383 my $deleted = grep {$_ eq $dev} @delete;
1384 die "invalid bootorder: device '$dev' does not exist'\n"
1385 if !$exists || $deleted;
1388 # remove legacy boot order settings if new one set
1389 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1390 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1391 if $conf->{bootdisk
};
1395 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1396 PVE
::QemuConfig-
>write_config($vmid, $conf);
1399 # remove pending changes when nothing changed
1400 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1401 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1402 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1404 return if !scalar(keys %{$conf->{pending
}});
1406 my $running = PVE
::QemuServer
::check_running
($vmid);
1408 # apply pending changes
1410 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1414 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1416 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1418 raise_param_exc
($errors) if scalar(keys %$errors);
1427 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1429 if ($background_delay) {
1431 # Note: It would be better to do that in the Event based HTTPServer
1432 # to avoid blocking call to sleep.
1434 my $end_time = time() + $background_delay;
1436 my $task = PVE
::Tools
::upid_decode
($upid);
1439 while (time() < $end_time) {
1440 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1442 sleep(1); # this gets interrupted when child process ends
1446 my $status = PVE
::Tools
::upid_read_status
($upid);
1447 return if !PVE
::Tools
::upid_status_is_error
($status);
1456 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1459 my $vm_config_perm_list = [
1464 'VM.Config.Network',
1466 'VM.Config.Options',
1467 'VM.Config.Cloudinit',
1470 __PACKAGE__-
>register_method({
1471 name
=> 'update_vm_async',
1472 path
=> '{vmid}/config',
1476 description
=> "Set virtual machine options (asynchrounous API).",
1478 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1481 additionalProperties
=> 0,
1482 properties
=> PVE
::QemuServer
::json_config_properties
(
1484 node
=> get_standard_option
('pve-node'),
1485 vmid
=> get_standard_option
('pve-vmid'),
1486 skiplock
=> get_standard_option
('skiplock'),
1488 type
=> 'string', format
=> 'pve-configid-list',
1489 description
=> "A list of settings you want to delete.",
1493 type
=> 'string', format
=> 'pve-configid-list',
1494 description
=> "Revert a pending change.",
1499 description
=> $opt_force_description,
1501 requires
=> 'delete',
1505 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1509 background_delay
=> {
1511 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1522 code
=> $update_vm_api,
1525 __PACKAGE__-
>register_method({
1526 name
=> 'update_vm',
1527 path
=> '{vmid}/config',
1531 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1533 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1536 additionalProperties
=> 0,
1537 properties
=> PVE
::QemuServer
::json_config_properties
(
1539 node
=> get_standard_option
('pve-node'),
1540 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1541 skiplock
=> get_standard_option
('skiplock'),
1543 type
=> 'string', format
=> 'pve-configid-list',
1544 description
=> "A list of settings you want to delete.",
1548 type
=> 'string', format
=> 'pve-configid-list',
1549 description
=> "Revert a pending change.",
1554 description
=> $opt_force_description,
1556 requires
=> 'delete',
1560 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1566 returns
=> { type
=> 'null' },
1569 &$update_vm_api($param, 1);
1574 __PACKAGE__-
>register_method({
1575 name
=> 'destroy_vm',
1580 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1581 ." and firewall rules",
1583 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1586 additionalProperties
=> 0,
1588 node
=> get_standard_option
('pve-node'),
1589 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1590 skiplock
=> get_standard_option
('skiplock'),
1593 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1596 'destroy-unreferenced-disks' => {
1598 description
=> "If set, destroy additionally all disks not referenced in the config"
1599 ." but with a matching VMID from all enabled storages.",
1611 my $rpcenv = PVE
::RPCEnvironment
::get
();
1612 my $authuser = $rpcenv->get_user();
1613 my $vmid = $param->{vmid
};
1615 my $skiplock = $param->{skiplock
};
1616 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1617 if $skiplock && $authuser ne 'root@pam';
1619 my $early_checks = sub {
1621 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1622 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1624 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1626 if (!$param->{purge
}) {
1627 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1629 # don't allow destroy if with replication jobs but no purge param
1630 my $repl_conf = PVE
::ReplicationConfig-
>new();
1631 $repl_conf->check_for_existing_jobs($vmid);
1634 die "VM $vmid is running - destroy failed\n"
1635 if PVE
::QemuServer
::check_running
($vmid);
1645 my $storecfg = PVE
::Storage
::config
();
1647 syslog
('info', "destroy VM $vmid: $upid\n");
1648 PVE
::QemuConfig-
>lock_config($vmid, sub {
1649 # repeat, config might have changed
1650 my $ha_managed = $early_checks->();
1652 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1654 PVE
::QemuServer
::destroy_vm
(
1657 $skiplock, { lock => 'destroyed' },
1658 $purge_unreferenced,
1661 PVE
::AccessControl
::remove_vm_access
($vmid);
1662 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1663 if ($param->{purge
}) {
1664 print "purging VM $vmid from related configurations..\n";
1665 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1666 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1669 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1670 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1674 # only now remove the zombie config, else we can have reuse race
1675 PVE
::QemuConfig-
>destroy_config($vmid);
1679 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1682 __PACKAGE__-
>register_method({
1684 path
=> '{vmid}/unlink',
1688 description
=> "Unlink/delete disk images.",
1690 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1693 additionalProperties
=> 0,
1695 node
=> get_standard_option
('pve-node'),
1696 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1698 type
=> 'string', format
=> 'pve-configid-list',
1699 description
=> "A list of disk IDs you want to delete.",
1703 description
=> $opt_force_description,
1708 returns
=> { type
=> 'null'},
1712 $param->{delete} = extract_param
($param, 'idlist');
1714 __PACKAGE__-
>update_vm($param);
1719 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1720 my $gen_rand_chars = sub {
1723 die "invalid length $length" if $length < 1;
1725 my $min = ord('!'); # first printable ascii
1727 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1728 die "failed to generate random bytes!\n"
1731 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1738 __PACKAGE__-
>register_method({
1740 path
=> '{vmid}/vncproxy',
1744 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1746 description
=> "Creates a TCP VNC proxy connections.",
1748 additionalProperties
=> 0,
1750 node
=> get_standard_option
('pve-node'),
1751 vmid
=> get_standard_option
('pve-vmid'),
1755 description
=> "starts websockify instead of vncproxy",
1757 'generate-password' => {
1761 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1766 additionalProperties
=> 0,
1768 user
=> { type
=> 'string' },
1769 ticket
=> { type
=> 'string' },
1772 description
=> "Returned if requested with 'generate-password' param."
1773 ." Consists of printable ASCII characters ('!' .. '~').",
1776 cert
=> { type
=> 'string' },
1777 port
=> { type
=> 'integer' },
1778 upid
=> { type
=> 'string' },
1784 my $rpcenv = PVE
::RPCEnvironment
::get
();
1786 my $authuser = $rpcenv->get_user();
1788 my $vmid = $param->{vmid
};
1789 my $node = $param->{node
};
1790 my $websocket = $param->{websocket
};
1792 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1796 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1797 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1800 my $authpath = "/vms/$vmid";
1802 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1803 my $password = $ticket;
1804 if ($param->{'generate-password'}) {
1805 $password = $gen_rand_chars->(8);
1808 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1814 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1815 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1816 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1817 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1818 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1820 $family = PVE
::Tools
::get_host_address_family
($node);
1823 my $port = PVE
::Tools
::next_vnc_port
($family);
1830 syslog
('info', "starting vnc proxy $upid\n");
1834 if (defined($serial)) {
1836 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1838 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1839 '-timeout', $timeout, '-authpath', $authpath,
1840 '-perm', 'Sys.Console'];
1842 if ($param->{websocket
}) {
1843 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1844 push @$cmd, '-notls', '-listen', 'localhost';
1847 push @$cmd, '-c', @$remcmd, @$termcmd;
1849 PVE
::Tools
::run_command
($cmd);
1853 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1855 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1857 my $sock = IO
::Socket
::IP-
>new(
1862 GetAddrInfoFlags
=> 0,
1863 ) or die "failed to create socket: $!\n";
1864 # Inside the worker we shouldn't have any previous alarms
1865 # running anyway...:
1867 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1869 accept(my $cli, $sock) or die "connection failed: $!\n";
1872 if (PVE
::Tools
::run_command
($cmd,
1873 output
=> '>&'.fileno($cli),
1874 input
=> '<&'.fileno($cli),
1877 die "Failed to run vncproxy.\n";
1884 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1886 PVE
::Tools
::wait_for_vnc_port
($port);
1895 $res->{password
} = $password if $param->{'generate-password'};
1900 __PACKAGE__-
>register_method({
1901 name
=> 'termproxy',
1902 path
=> '{vmid}/termproxy',
1906 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1908 description
=> "Creates a TCP proxy connections.",
1910 additionalProperties
=> 0,
1912 node
=> get_standard_option
('pve-node'),
1913 vmid
=> get_standard_option
('pve-vmid'),
1917 enum
=> [qw(serial0 serial1 serial2 serial3)],
1918 description
=> "opens a serial terminal (defaults to display)",
1923 additionalProperties
=> 0,
1925 user
=> { type
=> 'string' },
1926 ticket
=> { type
=> 'string' },
1927 port
=> { type
=> 'integer' },
1928 upid
=> { type
=> 'string' },
1934 my $rpcenv = PVE
::RPCEnvironment
::get
();
1936 my $authuser = $rpcenv->get_user();
1938 my $vmid = $param->{vmid
};
1939 my $node = $param->{node
};
1940 my $serial = $param->{serial
};
1942 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1944 if (!defined($serial)) {
1946 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1947 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1951 my $authpath = "/vms/$vmid";
1953 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1958 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1959 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1960 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1961 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1962 push @$remcmd, '--';
1964 $family = PVE
::Tools
::get_host_address_family
($node);
1967 my $port = PVE
::Tools
::next_vnc_port
($family);
1969 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1970 push @$termcmd, '-iface', $serial if $serial;
1975 syslog
('info', "starting qemu termproxy $upid\n");
1977 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1978 '--perm', 'VM.Console', '--'];
1979 push @$cmd, @$remcmd, @$termcmd;
1981 PVE
::Tools
::run_command
($cmd);
1984 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1986 PVE
::Tools
::wait_for_vnc_port
($port);
1996 __PACKAGE__-
>register_method({
1997 name
=> 'vncwebsocket',
1998 path
=> '{vmid}/vncwebsocket',
2001 description
=> "You also need to pass a valid ticket (vncticket).",
2002 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2004 description
=> "Opens a weksocket for VNC traffic.",
2006 additionalProperties
=> 0,
2008 node
=> get_standard_option
('pve-node'),
2009 vmid
=> get_standard_option
('pve-vmid'),
2011 description
=> "Ticket from previous call to vncproxy.",
2016 description
=> "Port number returned by previous vncproxy call.",
2026 port
=> { type
=> 'string' },
2032 my $rpcenv = PVE
::RPCEnvironment
::get
();
2034 my $authuser = $rpcenv->get_user();
2036 my $vmid = $param->{vmid
};
2037 my $node = $param->{node
};
2039 my $authpath = "/vms/$vmid";
2041 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2043 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2045 # Note: VNC ports are acessible from outside, so we do not gain any
2046 # security if we verify that $param->{port} belongs to VM $vmid. This
2047 # check is done by verifying the VNC ticket (inside VNC protocol).
2049 my $port = $param->{port
};
2051 return { port
=> $port };
2054 __PACKAGE__-
>register_method({
2055 name
=> 'spiceproxy',
2056 path
=> '{vmid}/spiceproxy',
2061 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2063 description
=> "Returns a SPICE configuration to connect to the VM.",
2065 additionalProperties
=> 0,
2067 node
=> get_standard_option
('pve-node'),
2068 vmid
=> get_standard_option
('pve-vmid'),
2069 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2072 returns
=> get_standard_option
('remote-viewer-config'),
2076 my $rpcenv = PVE
::RPCEnvironment
::get
();
2078 my $authuser = $rpcenv->get_user();
2080 my $vmid = $param->{vmid
};
2081 my $node = $param->{node
};
2082 my $proxy = $param->{proxy
};
2084 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2085 my $title = "VM $vmid";
2086 $title .= " - ". $conf->{name
} if $conf->{name
};
2088 my $port = PVE
::QemuServer
::spice_port
($vmid);
2090 my ($ticket, undef, $remote_viewer_config) =
2091 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2093 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2094 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2096 return $remote_viewer_config;
2099 __PACKAGE__-
>register_method({
2101 path
=> '{vmid}/status',
2104 description
=> "Directory index",
2109 additionalProperties
=> 0,
2111 node
=> get_standard_option
('pve-node'),
2112 vmid
=> get_standard_option
('pve-vmid'),
2120 subdir
=> { type
=> 'string' },
2123 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2129 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2132 { subdir
=> 'current' },
2133 { subdir
=> 'start' },
2134 { subdir
=> 'stop' },
2135 { subdir
=> 'reset' },
2136 { subdir
=> 'shutdown' },
2137 { subdir
=> 'suspend' },
2138 { subdir
=> 'reboot' },
2144 __PACKAGE__-
>register_method({
2145 name
=> 'vm_status',
2146 path
=> '{vmid}/status/current',
2149 protected
=> 1, # qemu pid files are only readable by root
2150 description
=> "Get virtual machine status.",
2152 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2155 additionalProperties
=> 0,
2157 node
=> get_standard_option
('pve-node'),
2158 vmid
=> get_standard_option
('pve-vmid'),
2164 %$PVE::QemuServer
::vmstatus_return_properties
,
2166 description
=> "HA manager service status.",
2170 description
=> "Qemu VGA configuration supports spice.",
2175 description
=> "Qemu GuestAgent enabled in config.",
2185 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2187 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2188 my $status = $vmstatus->{$param->{vmid
}};
2190 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2192 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2193 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2198 __PACKAGE__-
>register_method({
2200 path
=> '{vmid}/status/start',
2204 description
=> "Start virtual machine.",
2206 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2209 additionalProperties
=> 0,
2211 node
=> get_standard_option
('pve-node'),
2212 vmid
=> get_standard_option
('pve-vmid',
2213 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2214 skiplock
=> get_standard_option
('skiplock'),
2215 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2216 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2219 enum
=> ['secure', 'insecure'],
2220 description
=> "Migration traffic is encrypted using an SSH " .
2221 "tunnel by default. On secure, completely private networks " .
2222 "this can be disabled to increase performance.",
2225 migration_network
=> {
2226 type
=> 'string', format
=> 'CIDR',
2227 description
=> "CIDR of the (sub) network that is used for migration.",
2230 machine
=> get_standard_option
('pve-qemu-machine'),
2232 description
=> "Override QEMU's -cpu argument with the given string.",
2236 targetstorage
=> get_standard_option
('pve-targetstorage'),
2238 description
=> "Wait maximal timeout seconds.",
2241 default => 'max(30, vm memory in GiB)',
2252 my $rpcenv = PVE
::RPCEnvironment
::get
();
2253 my $authuser = $rpcenv->get_user();
2255 my $node = extract_param
($param, 'node');
2256 my $vmid = extract_param
($param, 'vmid');
2257 my $timeout = extract_param
($param, 'timeout');
2259 my $machine = extract_param
($param, 'machine');
2260 my $force_cpu = extract_param
($param, 'force-cpu');
2262 my $get_root_param = sub {
2263 my $value = extract_param
($param, $_[0]);
2264 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2265 if $value && $authuser ne 'root@pam';
2269 my $stateuri = $get_root_param->('stateuri');
2270 my $skiplock = $get_root_param->('skiplock');
2271 my $migratedfrom = $get_root_param->('migratedfrom');
2272 my $migration_type = $get_root_param->('migration_type');
2273 my $migration_network = $get_root_param->('migration_network');
2274 my $targetstorage = $get_root_param->('targetstorage');
2278 if ($targetstorage) {
2279 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2281 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2282 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2286 # read spice ticket from STDIN
2288 my $nbd_protocol_version = 0;
2289 my $replicated_volumes = {};
2290 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2291 while (defined(my $line = <STDIN
>)) {
2293 if ($line =~ m/^spice_ticket: (.+)$/) {
2295 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2296 $nbd_protocol_version = $1;
2297 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2298 $replicated_volumes->{$1} = 1;
2300 # fallback for old source node
2301 $spice_ticket = $line;
2306 PVE
::Cluster
::check_cfs_quorum
();
2308 my $storecfg = PVE
::Storage
::config
();
2310 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2314 print "Requesting HA start for VM $vmid\n";
2316 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2317 PVE
::Tools
::run_command
($cmd);
2321 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2328 syslog
('info', "start VM $vmid: $upid\n");
2330 my $migrate_opts = {
2331 migratedfrom
=> $migratedfrom,
2332 spice_ticket
=> $spice_ticket,
2333 network
=> $migration_network,
2334 type
=> $migration_type,
2335 storagemap
=> $storagemap,
2336 nbd_proto_version
=> $nbd_protocol_version,
2337 replicated_volumes
=> $replicated_volumes,
2341 statefile
=> $stateuri,
2342 skiplock
=> $skiplock,
2343 forcemachine
=> $machine,
2344 timeout
=> $timeout,
2345 forcecpu
=> $force_cpu,
2348 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2352 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2356 __PACKAGE__-
>register_method({
2358 path
=> '{vmid}/status/stop',
2362 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2363 "is akin to pulling the power plug of a running computer and may damage the VM data",
2365 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2368 additionalProperties
=> 0,
2370 node
=> get_standard_option
('pve-node'),
2371 vmid
=> get_standard_option
('pve-vmid',
2372 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2373 skiplock
=> get_standard_option
('skiplock'),
2374 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2376 description
=> "Wait maximal timeout seconds.",
2382 description
=> "Do not deactivate storage volumes.",
2395 my $rpcenv = PVE
::RPCEnvironment
::get
();
2396 my $authuser = $rpcenv->get_user();
2398 my $node = extract_param
($param, 'node');
2399 my $vmid = extract_param
($param, 'vmid');
2401 my $skiplock = extract_param
($param, 'skiplock');
2402 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2403 if $skiplock && $authuser ne 'root@pam';
2405 my $keepActive = extract_param
($param, 'keepActive');
2406 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2407 if $keepActive && $authuser ne 'root@pam';
2409 my $migratedfrom = extract_param
($param, 'migratedfrom');
2410 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2411 if $migratedfrom && $authuser ne 'root@pam';
2414 my $storecfg = PVE
::Storage
::config
();
2416 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2421 print "Requesting HA stop for VM $vmid\n";
2423 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2424 PVE
::Tools
::run_command
($cmd);
2428 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2434 syslog
('info', "stop VM $vmid: $upid\n");
2436 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2437 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2441 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2445 __PACKAGE__-
>register_method({
2447 path
=> '{vmid}/status/reset',
2451 description
=> "Reset virtual machine.",
2453 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2456 additionalProperties
=> 0,
2458 node
=> get_standard_option
('pve-node'),
2459 vmid
=> get_standard_option
('pve-vmid',
2460 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2461 skiplock
=> get_standard_option
('skiplock'),
2470 my $rpcenv = PVE
::RPCEnvironment
::get
();
2472 my $authuser = $rpcenv->get_user();
2474 my $node = extract_param
($param, 'node');
2476 my $vmid = extract_param
($param, 'vmid');
2478 my $skiplock = extract_param
($param, 'skiplock');
2479 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2480 if $skiplock && $authuser ne 'root@pam';
2482 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2487 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2492 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2495 __PACKAGE__-
>register_method({
2496 name
=> 'vm_shutdown',
2497 path
=> '{vmid}/status/shutdown',
2501 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2502 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2504 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2507 additionalProperties
=> 0,
2509 node
=> get_standard_option
('pve-node'),
2510 vmid
=> get_standard_option
('pve-vmid',
2511 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2512 skiplock
=> get_standard_option
('skiplock'),
2514 description
=> "Wait maximal timeout seconds.",
2520 description
=> "Make sure the VM stops.",
2526 description
=> "Do not deactivate storage volumes.",
2539 my $rpcenv = PVE
::RPCEnvironment
::get
();
2540 my $authuser = $rpcenv->get_user();
2542 my $node = extract_param
($param, 'node');
2543 my $vmid = extract_param
($param, 'vmid');
2545 my $skiplock = extract_param
($param, 'skiplock');
2546 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2547 if $skiplock && $authuser ne 'root@pam';
2549 my $keepActive = extract_param
($param, 'keepActive');
2550 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2551 if $keepActive && $authuser ne 'root@pam';
2553 my $storecfg = PVE
::Storage
::config
();
2557 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2558 # otherwise, we will infer a shutdown command, but run into the timeout,
2559 # then when the vm is resumed, it will instantly shutdown
2561 # checking the qmp status here to get feedback to the gui/cli/api
2562 # and the status query should not take too long
2563 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2564 if ($param->{forceStop
}) {
2565 warn "VM is paused - stop instead of shutdown\n";
2568 die "VM is paused - cannot shutdown\n";
2572 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2574 my $timeout = $param->{timeout
} // 60;
2578 print "Requesting HA stop for VM $vmid\n";
2580 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2581 PVE
::Tools
::run_command
($cmd);
2585 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2592 syslog
('info', "shutdown VM $vmid: $upid\n");
2594 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2595 $shutdown, $param->{forceStop
}, $keepActive);
2599 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2603 __PACKAGE__-
>register_method({
2604 name
=> 'vm_reboot',
2605 path
=> '{vmid}/status/reboot',
2609 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2611 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2614 additionalProperties
=> 0,
2616 node
=> get_standard_option
('pve-node'),
2617 vmid
=> get_standard_option
('pve-vmid',
2618 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2620 description
=> "Wait maximal timeout seconds for the shutdown.",
2633 my $rpcenv = PVE
::RPCEnvironment
::get
();
2634 my $authuser = $rpcenv->get_user();
2636 my $node = extract_param
($param, 'node');
2637 my $vmid = extract_param
($param, 'vmid');
2639 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2641 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2646 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2647 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2651 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2654 __PACKAGE__-
>register_method({
2655 name
=> 'vm_suspend',
2656 path
=> '{vmid}/status/suspend',
2660 description
=> "Suspend virtual machine.",
2662 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2663 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2664 " on the storage for the vmstate.",
2665 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2668 additionalProperties
=> 0,
2670 node
=> get_standard_option
('pve-node'),
2671 vmid
=> get_standard_option
('pve-vmid',
2672 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2673 skiplock
=> get_standard_option
('skiplock'),
2678 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2680 statestorage
=> get_standard_option
('pve-storage-id', {
2681 description
=> "The storage for the VM state",
2682 requires
=> 'todisk',
2684 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2694 my $rpcenv = PVE
::RPCEnvironment
::get
();
2695 my $authuser = $rpcenv->get_user();
2697 my $node = extract_param
($param, 'node');
2698 my $vmid = extract_param
($param, 'vmid');
2700 my $todisk = extract_param
($param, 'todisk') // 0;
2702 my $statestorage = extract_param
($param, 'statestorage');
2704 my $skiplock = extract_param
($param, 'skiplock');
2705 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2706 if $skiplock && $authuser ne 'root@pam';
2708 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2710 die "Cannot suspend HA managed VM to disk\n"
2711 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2713 # early check for storage permission, for better user feedback
2715 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2717 if (!$statestorage) {
2718 # get statestorage from config if none is given
2719 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2720 my $storecfg = PVE
::Storage
::config
();
2721 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2724 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2730 syslog
('info', "suspend VM $vmid: $upid\n");
2732 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2737 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2738 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2741 __PACKAGE__-
>register_method({
2742 name
=> 'vm_resume',
2743 path
=> '{vmid}/status/resume',
2747 description
=> "Resume virtual machine.",
2749 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2752 additionalProperties
=> 0,
2754 node
=> get_standard_option
('pve-node'),
2755 vmid
=> get_standard_option
('pve-vmid',
2756 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2757 skiplock
=> get_standard_option
('skiplock'),
2758 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2768 my $rpcenv = PVE
::RPCEnvironment
::get
();
2770 my $authuser = $rpcenv->get_user();
2772 my $node = extract_param
($param, 'node');
2774 my $vmid = extract_param
($param, 'vmid');
2776 my $skiplock = extract_param
($param, 'skiplock');
2777 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2778 if $skiplock && $authuser ne 'root@pam';
2780 my $nocheck = extract_param
($param, 'nocheck');
2781 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2782 if $nocheck && $authuser ne 'root@pam';
2784 my $to_disk_suspended;
2786 PVE
::QemuConfig-
>lock_config($vmid, sub {
2787 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2788 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2792 die "VM $vmid not running\n"
2793 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2798 syslog
('info', "resume VM $vmid: $upid\n");
2800 if (!$to_disk_suspended) {
2801 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2803 my $storecfg = PVE
::Storage
::config
();
2804 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2810 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2813 __PACKAGE__-
>register_method({
2814 name
=> 'vm_sendkey',
2815 path
=> '{vmid}/sendkey',
2819 description
=> "Send key event to virtual machine.",
2821 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2824 additionalProperties
=> 0,
2826 node
=> get_standard_option
('pve-node'),
2827 vmid
=> get_standard_option
('pve-vmid',
2828 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2829 skiplock
=> get_standard_option
('skiplock'),
2831 description
=> "The key (qemu monitor encoding).",
2836 returns
=> { type
=> 'null'},
2840 my $rpcenv = PVE
::RPCEnvironment
::get
();
2842 my $authuser = $rpcenv->get_user();
2844 my $node = extract_param
($param, 'node');
2846 my $vmid = extract_param
($param, 'vmid');
2848 my $skiplock = extract_param
($param, 'skiplock');
2849 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2850 if $skiplock && $authuser ne 'root@pam';
2852 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2857 __PACKAGE__-
>register_method({
2858 name
=> 'vm_feature',
2859 path
=> '{vmid}/feature',
2863 description
=> "Check if feature for virtual machine is available.",
2865 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2868 additionalProperties
=> 0,
2870 node
=> get_standard_option
('pve-node'),
2871 vmid
=> get_standard_option
('pve-vmid'),
2873 description
=> "Feature to check.",
2875 enum
=> [ 'snapshot', 'clone', 'copy' ],
2877 snapname
=> get_standard_option
('pve-snapshot-name', {
2885 hasFeature
=> { type
=> 'boolean' },
2888 items
=> { type
=> 'string' },
2895 my $node = extract_param
($param, 'node');
2897 my $vmid = extract_param
($param, 'vmid');
2899 my $snapname = extract_param
($param, 'snapname');
2901 my $feature = extract_param
($param, 'feature');
2903 my $running = PVE
::QemuServer
::check_running
($vmid);
2905 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2908 my $snap = $conf->{snapshots
}->{$snapname};
2909 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2912 my $storecfg = PVE
::Storage
::config
();
2914 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2915 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2918 hasFeature
=> $hasFeature,
2919 nodes
=> [ keys %$nodelist ],
2923 __PACKAGE__-
>register_method({
2925 path
=> '{vmid}/clone',
2929 description
=> "Create a copy of virtual machine/template.",
2931 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2932 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2933 "'Datastore.AllocateSpace' on any used storage.",
2936 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2938 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2939 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2944 additionalProperties
=> 0,
2946 node
=> get_standard_option
('pve-node'),
2947 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2948 newid
=> get_standard_option
('pve-vmid', {
2949 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2950 description
=> 'VMID for the clone.' }),
2953 type
=> 'string', format
=> 'dns-name',
2954 description
=> "Set a name for the new VM.",
2959 description
=> "Description for the new VM.",
2963 type
=> 'string', format
=> 'pve-poolid',
2964 description
=> "Add the new VM to the specified pool.",
2966 snapname
=> get_standard_option
('pve-snapshot-name', {
2969 storage
=> get_standard_option
('pve-storage-id', {
2970 description
=> "Target storage for full clone.",
2974 description
=> "Target format for file storage. Only valid for full clone.",
2977 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2982 description
=> "Create a full copy of all disks. This is always done when " .
2983 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2985 target
=> get_standard_option
('pve-node', {
2986 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2990 description
=> "Override I/O bandwidth limit (in KiB/s).",
2994 default => 'clone limit from datacenter or storage config',
3004 my $rpcenv = PVE
::RPCEnvironment
::get
();
3005 my $authuser = $rpcenv->get_user();
3007 my $node = extract_param
($param, 'node');
3008 my $vmid = extract_param
($param, 'vmid');
3009 my $newid = extract_param
($param, 'newid');
3010 my $pool = extract_param
($param, 'pool');
3011 $rpcenv->check_pool_exist($pool) if defined($pool);
3013 my $snapname = extract_param
($param, 'snapname');
3014 my $storage = extract_param
($param, 'storage');
3015 my $format = extract_param
($param, 'format');
3016 my $target = extract_param
($param, 'target');
3018 my $localnode = PVE
::INotify
::nodename
();
3020 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3024 PVE
::Cluster
::check_node_exists
($target) if $target;
3026 my $storecfg = PVE
::Storage
::config
();
3029 # check if storage is enabled on local node
3030 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3032 # check if storage is available on target node
3033 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3034 # clone only works if target storage is shared
3035 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3036 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3040 PVE
::Cluster
::check_cfs_quorum
();
3042 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3045 # do all tests after lock but before forking worker - if possible
3047 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3048 PVE
::QemuConfig-
>check_lock($conf);
3050 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3051 die "unexpected state change\n" if $verify_running != $running;
3053 die "snapshot '$snapname' does not exist\n"
3054 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3056 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3058 die "parameter 'storage' not allowed for linked clones\n"
3059 if defined($storage) && !$full;
3061 die "parameter 'format' not allowed for linked clones\n"
3062 if defined($format) && !$full;
3064 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3066 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3068 die "can't clone VM to node '$target' (VM uses local storage)\n"
3069 if $target && !$sharedvm;
3071 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3072 die "unable to create VM $newid: config file already exists\n"
3075 my $newconf = { lock => 'clone' };
3080 foreach my $opt (keys %$oldconf) {
3081 my $value = $oldconf->{$opt};
3083 # do not copy snapshot related info
3084 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3085 $opt eq 'vmstate' || $opt eq 'snapstate';
3087 # no need to copy unused images, because VMID(owner) changes anyways
3088 next if $opt =~ m/^unused\d+$/;
3090 # always change MAC! address
3091 if ($opt =~ m/^net(\d+)$/) {
3092 my $net = PVE
::QemuServer
::parse_net
($value);
3093 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3094 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3095 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3096 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3097 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3098 die "unable to parse drive options for '$opt'\n" if !$drive;
3099 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3100 $newconf->{$opt} = $value; # simply copy configuration
3102 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3103 die "Full clone feature is not supported for drive '$opt'\n"
3104 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3105 $fullclone->{$opt} = 1;
3107 # not full means clone instead of copy
3108 die "Linked clone feature is not supported for drive '$opt'\n"
3109 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3111 $drives->{$opt} = $drive;
3112 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3113 push @$vollist, $drive->{file
};
3116 # copy everything else
3117 $newconf->{$opt} = $value;
3121 # auto generate a new uuid
3122 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3123 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3124 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3125 # auto generate a new vmgenid only if the option was set for template
3126 if ($newconf->{vmgenid
}) {
3127 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3130 delete $newconf->{template
};
3132 if ($param->{name
}) {
3133 $newconf->{name
} = $param->{name
};
3135 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3138 if ($param->{description
}) {
3139 $newconf->{description
} = $param->{description
};
3142 # create empty/temp config - this fails if VM already exists on other node
3143 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3144 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3149 my $newvollist = [];
3156 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3158 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3160 my $bwlimit = extract_param
($param, 'bwlimit');
3162 my $total_jobs = scalar(keys %{$drives});
3165 foreach my $opt (sort keys %$drives) {
3166 my $drive = $drives->{$opt};
3167 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3168 my $completion = $skipcomplete ?
'skip' : 'complete';
3170 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3171 my $storage_list = [ $src_sid ];
3172 push @$storage_list, $storage if defined($storage);
3173 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3175 my $newdrive = PVE
::QemuServer
::clone_disk
(
3194 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3196 PVE
::QemuConfig-
>write_config($newid, $newconf);
3200 delete $newconf->{lock};
3202 # do not write pending changes
3203 if (my @changes = keys %{$newconf->{pending
}}) {
3204 my $pending = join(',', @changes);
3205 warn "found pending changes for '$pending', discarding for clone\n";
3206 delete $newconf->{pending
};
3209 PVE
::QemuConfig-
>write_config($newid, $newconf);
3212 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3213 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3214 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3216 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3217 die "Failed to move config to node '$target' - rename failed: $!\n"
3218 if !rename($conffile, $newconffile);
3221 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3224 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3225 sleep 1; # some storage like rbd need to wait before release volume - really?
3227 foreach my $volid (@$newvollist) {
3228 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3232 PVE
::Firewall
::remove_vmfw_conf
($newid);
3234 unlink $conffile; # avoid races -> last thing before die
3236 die "clone failed: $err";
3242 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3244 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3247 # Aquire exclusive lock lock for $newid
3248 my $lock_target_vm = sub {
3249 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3252 # exclusive lock if VM is running - else shared lock is enough;
3254 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3256 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3260 __PACKAGE__-
>register_method({
3261 name
=> 'move_vm_disk',
3262 path
=> '{vmid}/move_disk',
3266 description
=> "Move volume to different storage.",
3268 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3270 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3271 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3275 additionalProperties
=> 0,
3277 node
=> get_standard_option
('pve-node'),
3278 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3281 description
=> "The disk you want to move.",
3282 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3284 storage
=> get_standard_option
('pve-storage-id', {
3285 description
=> "Target storage.",
3286 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3290 description
=> "Target Format.",
3291 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3296 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3302 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3307 description
=> "Override I/O bandwidth limit (in KiB/s).",
3311 default => 'move limit from datacenter or storage config',
3317 description
=> "the task ID.",
3322 my $rpcenv = PVE
::RPCEnvironment
::get
();
3323 my $authuser = $rpcenv->get_user();
3325 my $node = extract_param
($param, 'node');
3326 my $vmid = extract_param
($param, 'vmid');
3327 my $digest = extract_param
($param, 'digest');
3328 my $disk = extract_param
($param, 'disk');
3329 my $storeid = extract_param
($param, 'storage');
3330 my $format = extract_param
($param, 'format');
3332 my $storecfg = PVE
::Storage
::config
();
3334 my $updatefn = sub {
3335 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3336 PVE
::QemuConfig-
>check_lock($conf);
3338 die "VM config checksum missmatch (file change by other user?)\n"
3339 if $digest && $digest ne $conf->{digest
};
3341 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3343 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3345 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3346 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3348 my $old_volid = $drive->{file
};
3350 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3351 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3355 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3356 (!$format || !$oldfmt || $oldfmt eq $format);
3358 # this only checks snapshots because $disk is passed!
3359 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3360 die "you can't move a disk with snapshots and delete the source\n"
3361 if $snapshotted && $param->{delete};
3363 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3365 my $running = PVE
::QemuServer
::check_running
($vmid);
3367 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3370 my $newvollist = [];
3376 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3378 warn "moving disk with snapshots, snapshots will not be moved!\n"
3381 my $bwlimit = extract_param
($param, 'bwlimit');
3382 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3384 my $newdrive = PVE
::QemuServer
::clone_disk
(
3402 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3404 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3406 # convert moved disk to base if part of template
3407 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3408 if PVE
::QemuConfig-
>is_template($conf);
3410 PVE
::QemuConfig-
>write_config($vmid, $conf);
3412 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3413 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3414 eval { mon_cmd
($vmid, "guest-fstrim") };
3418 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3419 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3425 foreach my $volid (@$newvollist) {
3426 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3429 die "storage migration failed: $err";
3432 if ($param->{delete}) {
3434 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3435 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3441 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3444 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3447 my $check_vm_disks_local = sub {
3448 my ($storecfg, $vmconf, $vmid) = @_;
3450 my $local_disks = {};
3452 # add some more information to the disks e.g. cdrom
3453 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3454 my ($volid, $attr) = @_;
3456 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3458 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3459 return if $scfg->{shared
};
3461 # The shared attr here is just a special case where the vdisk
3462 # is marked as shared manually
3463 return if $attr->{shared
};
3464 return if $attr->{cdrom
} and $volid eq "none";
3466 if (exists $local_disks->{$volid}) {
3467 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3469 $local_disks->{$volid} = $attr;
3470 # ensure volid is present in case it's needed
3471 $local_disks->{$volid}->{volid
} = $volid;
3475 return $local_disks;
3478 __PACKAGE__-
>register_method({
3479 name
=> 'migrate_vm_precondition',
3480 path
=> '{vmid}/migrate',
3484 description
=> "Get preconditions for migration.",
3486 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3489 additionalProperties
=> 0,
3491 node
=> get_standard_option
('pve-node'),
3492 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3493 target
=> get_standard_option
('pve-node', {
3494 description
=> "Target node.",
3495 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3503 running
=> { type
=> 'boolean' },
3507 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3509 not_allowed_nodes
=> {
3512 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3516 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3518 local_resources
=> {
3520 description
=> "List local resources e.g. pci, usb"
3527 my $rpcenv = PVE
::RPCEnvironment
::get
();
3529 my $authuser = $rpcenv->get_user();
3531 PVE
::Cluster
::check_cfs_quorum
();
3535 my $vmid = extract_param
($param, 'vmid');
3536 my $target = extract_param
($param, 'target');
3537 my $localnode = PVE
::INotify
::nodename
();
3541 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3542 my $storecfg = PVE
::Storage
::config
();
3545 # try to detect errors early
3546 PVE
::QemuConfig-
>check_lock($vmconf);
3548 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3550 # if vm is not running, return target nodes where local storage is available
3551 # for offline migration
3552 if (!$res->{running
}) {
3553 $res->{allowed_nodes
} = [];
3554 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3555 delete $checked_nodes->{$localnode};
3557 foreach my $node (keys %$checked_nodes) {
3558 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3559 push @{$res->{allowed_nodes
}}, $node;
3563 $res->{not_allowed_nodes
} = $checked_nodes;
3567 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3568 $res->{local_disks
} = [ values %$local_disks ];;
3570 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3572 $res->{local_resources
} = $local_resources;
3579 __PACKAGE__-
>register_method({
3580 name
=> 'migrate_vm',
3581 path
=> '{vmid}/migrate',
3585 description
=> "Migrate virtual machine. Creates a new migration task.",
3587 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3590 additionalProperties
=> 0,
3592 node
=> get_standard_option
('pve-node'),
3593 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3594 target
=> get_standard_option
('pve-node', {
3595 description
=> "Target node.",
3596 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3600 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3605 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3610 enum
=> ['secure', 'insecure'],
3611 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3614 migration_network
=> {
3615 type
=> 'string', format
=> 'CIDR',
3616 description
=> "CIDR of the (sub) network that is used for migration.",
3619 "with-local-disks" => {
3621 description
=> "Enable live storage migration for local disk",
3624 targetstorage
=> get_standard_option
('pve-targetstorage', {
3625 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3628 description
=> "Override I/O bandwidth limit (in KiB/s).",
3632 default => 'migrate limit from datacenter or storage config',
3638 description
=> "the task ID.",
3643 my $rpcenv = PVE
::RPCEnvironment
::get
();
3644 my $authuser = $rpcenv->get_user();
3646 my $target = extract_param
($param, 'target');
3648 my $localnode = PVE
::INotify
::nodename
();
3649 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3651 PVE
::Cluster
::check_cfs_quorum
();
3653 PVE
::Cluster
::check_node_exists
($target);
3655 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3657 my $vmid = extract_param
($param, 'vmid');
3659 raise_param_exc
({ force
=> "Only root may use this option." })
3660 if $param->{force
} && $authuser ne 'root@pam';
3662 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3663 if $param->{migration_type
} && $authuser ne 'root@pam';
3665 # allow root only until better network permissions are available
3666 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3667 if $param->{migration_network
} && $authuser ne 'root@pam';
3670 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3672 # try to detect errors early
3674 PVE
::QemuConfig-
>check_lock($conf);
3676 if (PVE
::QemuServer
::check_running
($vmid)) {
3677 die "can't migrate running VM without --online\n" if !$param->{online
};
3679 my $repl_conf = PVE
::ReplicationConfig-
>new();
3680 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3681 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3682 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3683 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3684 "target. Use 'force' to override.\n";
3687 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3688 $param->{online
} = 0;
3691 my $storecfg = PVE
::Storage
::config
();
3693 if (my $targetstorage = $param->{targetstorage
}) {
3694 my $check_storage = sub {
3695 my ($target_sid) = @_;
3696 PVE
::Storage
::storage_check_enabled
($storecfg, $target_sid, $target);
3697 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3698 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3699 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3700 if !$scfg->{content
}->{images
};
3703 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3704 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3707 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3708 if !defined($storagemap->{identity
});
3710 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3711 $check_storage->($target_sid);
3714 $check_storage->($storagemap->{default})
3715 if $storagemap->{default};
3717 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3718 if $storagemap->{identity
};
3720 $param->{storagemap
} = $storagemap;
3722 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3725 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3730 print "Requesting HA migration for VM $vmid to node $target\n";
3732 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3733 PVE
::Tools
::run_command
($cmd);
3737 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3742 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3746 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3749 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3754 __PACKAGE__-
>register_method({
3756 path
=> '{vmid}/monitor',
3760 description
=> "Execute Qemu monitor commands.",
3762 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3763 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3766 additionalProperties
=> 0,
3768 node
=> get_standard_option
('pve-node'),
3769 vmid
=> get_standard_option
('pve-vmid'),
3772 description
=> "The monitor command.",
3776 returns
=> { type
=> 'string'},
3780 my $rpcenv = PVE
::RPCEnvironment
::get
();
3781 my $authuser = $rpcenv->get_user();
3784 my $command = shift;
3785 return $command =~ m/^\s*info(\s+|$)/
3786 || $command =~ m/^\s*help\s*$/;
3789 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3790 if !&$is_ro($param->{command
});
3792 my $vmid = $param->{vmid
};
3794 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3798 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3800 $res = "ERROR: $@" if $@;
3805 __PACKAGE__-
>register_method({
3806 name
=> 'resize_vm',
3807 path
=> '{vmid}/resize',
3811 description
=> "Extend volume size.",
3813 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3816 additionalProperties
=> 0,
3818 node
=> get_standard_option
('pve-node'),
3819 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3820 skiplock
=> get_standard_option
('skiplock'),
3823 description
=> "The disk you want to resize.",
3824 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3828 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3829 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.",
3833 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3839 returns
=> { type
=> 'null'},
3843 my $rpcenv = PVE
::RPCEnvironment
::get
();
3845 my $authuser = $rpcenv->get_user();
3847 my $node = extract_param
($param, 'node');
3849 my $vmid = extract_param
($param, 'vmid');
3851 my $digest = extract_param
($param, 'digest');
3853 my $disk = extract_param
($param, 'disk');
3855 my $sizestr = extract_param
($param, 'size');
3857 my $skiplock = extract_param
($param, 'skiplock');
3858 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3859 if $skiplock && $authuser ne 'root@pam';
3861 my $storecfg = PVE
::Storage
::config
();
3863 my $updatefn = sub {
3865 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3867 die "checksum missmatch (file change by other user?)\n"
3868 if $digest && $digest ne $conf->{digest
};
3869 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3871 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3873 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3875 my (undef, undef, undef, undef, undef, undef, $format) =
3876 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3878 die "can't resize volume: $disk if snapshot exists\n"
3879 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3881 my $volid = $drive->{file
};
3883 die "disk '$disk' has no associated volume\n" if !$volid;
3885 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3887 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3889 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3891 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3892 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3894 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3896 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3897 my ($ext, $newsize, $unit) = ($1, $2, $4);
3900 $newsize = $newsize * 1024;
3901 } elsif ($unit eq 'M') {
3902 $newsize = $newsize * 1024 * 1024;
3903 } elsif ($unit eq 'G') {
3904 $newsize = $newsize * 1024 * 1024 * 1024;
3905 } elsif ($unit eq 'T') {
3906 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3909 $newsize += $size if $ext;
3910 $newsize = int($newsize);
3912 die "shrinking disks is not supported\n" if $newsize < $size;
3914 return if $size == $newsize;
3916 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3918 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3920 $drive->{size
} = $newsize;
3921 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3923 PVE
::QemuConfig-
>write_config($vmid, $conf);
3926 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3930 __PACKAGE__-
>register_method({
3931 name
=> 'snapshot_list',
3932 path
=> '{vmid}/snapshot',
3934 description
=> "List all snapshots.",
3936 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3939 protected
=> 1, # qemu pid files are only readable by root
3941 additionalProperties
=> 0,
3943 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3944 node
=> get_standard_option
('pve-node'),
3953 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3957 description
=> "Snapshot includes RAM.",
3962 description
=> "Snapshot description.",
3966 description
=> "Snapshot creation time",
3968 renderer
=> 'timestamp',
3972 description
=> "Parent snapshot identifier.",
3978 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3983 my $vmid = $param->{vmid
};
3985 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3986 my $snaphash = $conf->{snapshots
} || {};
3990 foreach my $name (keys %$snaphash) {
3991 my $d = $snaphash->{$name};
3994 snaptime
=> $d->{snaptime
} || 0,
3995 vmstate
=> $d->{vmstate
} ?
1 : 0,
3996 description
=> $d->{description
} || '',
3998 $item->{parent
} = $d->{parent
} if $d->{parent
};
3999 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4003 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4006 digest
=> $conf->{digest
},
4007 running
=> $running,
4008 description
=> "You are here!",
4010 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4012 push @$res, $current;
4017 __PACKAGE__-
>register_method({
4019 path
=> '{vmid}/snapshot',
4023 description
=> "Snapshot a VM.",
4025 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4028 additionalProperties
=> 0,
4030 node
=> get_standard_option
('pve-node'),
4031 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4032 snapname
=> get_standard_option
('pve-snapshot-name'),
4036 description
=> "Save the vmstate",
4041 description
=> "A textual description or comment.",
4047 description
=> "the task ID.",
4052 my $rpcenv = PVE
::RPCEnvironment
::get
();
4054 my $authuser = $rpcenv->get_user();
4056 my $node = extract_param
($param, 'node');
4058 my $vmid = extract_param
($param, 'vmid');
4060 my $snapname = extract_param
($param, 'snapname');
4062 die "unable to use snapshot name 'current' (reserved name)\n"
4063 if $snapname eq 'current';
4065 die "unable to use snapshot name 'pending' (reserved name)\n"
4066 if lc($snapname) eq 'pending';
4069 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4070 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4071 $param->{description
});
4074 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4077 __PACKAGE__-
>register_method({
4078 name
=> 'snapshot_cmd_idx',
4079 path
=> '{vmid}/snapshot/{snapname}',
4086 additionalProperties
=> 0,
4088 vmid
=> get_standard_option
('pve-vmid'),
4089 node
=> get_standard_option
('pve-node'),
4090 snapname
=> get_standard_option
('pve-snapshot-name'),
4099 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4106 push @$res, { cmd
=> 'rollback' };
4107 push @$res, { cmd
=> 'config' };
4112 __PACKAGE__-
>register_method({
4113 name
=> 'update_snapshot_config',
4114 path
=> '{vmid}/snapshot/{snapname}/config',
4118 description
=> "Update snapshot metadata.",
4120 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4123 additionalProperties
=> 0,
4125 node
=> get_standard_option
('pve-node'),
4126 vmid
=> get_standard_option
('pve-vmid'),
4127 snapname
=> get_standard_option
('pve-snapshot-name'),
4131 description
=> "A textual description or comment.",
4135 returns
=> { type
=> 'null' },
4139 my $rpcenv = PVE
::RPCEnvironment
::get
();
4141 my $authuser = $rpcenv->get_user();
4143 my $vmid = extract_param
($param, 'vmid');
4145 my $snapname = extract_param
($param, 'snapname');
4147 return if !defined($param->{description
});
4149 my $updatefn = sub {
4151 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4153 PVE
::QemuConfig-
>check_lock($conf);
4155 my $snap = $conf->{snapshots
}->{$snapname};
4157 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4159 $snap->{description
} = $param->{description
} if defined($param->{description
});
4161 PVE
::QemuConfig-
>write_config($vmid, $conf);
4164 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4169 __PACKAGE__-
>register_method({
4170 name
=> 'get_snapshot_config',
4171 path
=> '{vmid}/snapshot/{snapname}/config',
4174 description
=> "Get snapshot configuration",
4176 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4179 additionalProperties
=> 0,
4181 node
=> get_standard_option
('pve-node'),
4182 vmid
=> get_standard_option
('pve-vmid'),
4183 snapname
=> get_standard_option
('pve-snapshot-name'),
4186 returns
=> { type
=> "object" },
4190 my $rpcenv = PVE
::RPCEnvironment
::get
();
4192 my $authuser = $rpcenv->get_user();
4194 my $vmid = extract_param
($param, 'vmid');
4196 my $snapname = extract_param
($param, 'snapname');
4198 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4200 my $snap = $conf->{snapshots
}->{$snapname};
4202 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4207 __PACKAGE__-
>register_method({
4209 path
=> '{vmid}/snapshot/{snapname}/rollback',
4213 description
=> "Rollback VM state to specified snapshot.",
4215 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4218 additionalProperties
=> 0,
4220 node
=> get_standard_option
('pve-node'),
4221 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4222 snapname
=> get_standard_option
('pve-snapshot-name'),
4227 description
=> "the task ID.",
4232 my $rpcenv = PVE
::RPCEnvironment
::get
();
4234 my $authuser = $rpcenv->get_user();
4236 my $node = extract_param
($param, 'node');
4238 my $vmid = extract_param
($param, 'vmid');
4240 my $snapname = extract_param
($param, 'snapname');
4243 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4244 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4248 # hold migration lock, this makes sure that nobody create replication snapshots
4249 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4252 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4255 __PACKAGE__-
>register_method({
4256 name
=> 'delsnapshot',
4257 path
=> '{vmid}/snapshot/{snapname}',
4261 description
=> "Delete a VM snapshot.",
4263 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4266 additionalProperties
=> 0,
4268 node
=> get_standard_option
('pve-node'),
4269 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4270 snapname
=> get_standard_option
('pve-snapshot-name'),
4274 description
=> "For removal from config file, even if removing disk snapshots fails.",
4280 description
=> "the task ID.",
4285 my $rpcenv = PVE
::RPCEnvironment
::get
();
4287 my $authuser = $rpcenv->get_user();
4289 my $node = extract_param
($param, 'node');
4291 my $vmid = extract_param
($param, 'vmid');
4293 my $snapname = extract_param
($param, 'snapname');
4296 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4297 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4300 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4303 __PACKAGE__-
>register_method({
4305 path
=> '{vmid}/template',
4309 description
=> "Create a Template.",
4311 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4312 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4315 additionalProperties
=> 0,
4317 node
=> get_standard_option
('pve-node'),
4318 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4322 description
=> "If you want to convert only 1 disk to base image.",
4323 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4328 returns
=> { type
=> 'null'},
4332 my $rpcenv = PVE
::RPCEnvironment
::get
();
4334 my $authuser = $rpcenv->get_user();
4336 my $node = extract_param
($param, 'node');
4338 my $vmid = extract_param
($param, 'vmid');
4340 my $disk = extract_param
($param, 'disk');
4342 my $updatefn = sub {
4344 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4346 PVE
::QemuConfig-
>check_lock($conf);
4348 die "unable to create template, because VM contains snapshots\n"
4349 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4351 die "you can't convert a template to a template\n"
4352 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4354 die "you can't convert a VM to template if VM is running\n"
4355 if PVE
::QemuServer
::check_running
($vmid);
4358 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4361 $conf->{template
} = 1;
4362 PVE
::QemuConfig-
>write_config($vmid, $conf);
4364 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4367 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4371 __PACKAGE__-
>register_method({
4372 name
=> 'cloudinit_generated_config_dump',
4373 path
=> '{vmid}/cloudinit/dump',
4376 description
=> "Get automatically generated cloudinit config.",
4378 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4381 additionalProperties
=> 0,
4383 node
=> get_standard_option
('pve-node'),
4384 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4386 description
=> 'Config type.',
4388 enum
=> ['user', 'network', 'meta'],
4398 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4400 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});