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 foreach my $opt (@delete) {
1267 $modified->{$opt} = 1;
1268 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1270 # value of what we want to delete, independent if pending or not
1271 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1272 if (!defined($val)) {
1273 warn "cannot delete '$opt' - not set in current configuration!\n";
1274 $modified->{$opt} = 0;
1277 my $is_pending_val = defined($conf->{pending
}->{$opt});
1278 delete $conf->{pending
}->{$opt};
1280 # remove from bootorder if necessary
1281 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1282 @bootorder = grep {$_ ne $opt} @bootorder;
1283 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1284 $modified->{boot
} = 1;
1287 if ($opt =~ m/^unused/) {
1288 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1289 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1290 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1291 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1292 delete $conf->{$opt};
1293 PVE
::QemuConfig-
>write_config($vmid, $conf);
1295 } elsif ($opt eq 'vmstate') {
1296 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1297 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1298 delete $conf->{$opt};
1299 PVE
::QemuConfig-
>write_config($vmid, $conf);
1301 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1302 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1303 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1304 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1305 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1307 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1309 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1311 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1312 PVE
::QemuConfig-
>write_config($vmid, $conf);
1313 } elsif ($opt =~ m/^serial\d+$/) {
1314 if ($val eq 'socket') {
1315 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1316 } elsif ($authuser ne 'root@pam') {
1317 die "only root can delete '$opt' config for real devices\n";
1319 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1320 PVE
::QemuConfig-
>write_config($vmid, $conf);
1321 } elsif ($opt =~ m/^usb\d+$/) {
1322 if ($val =~ m/spice/) {
1323 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1324 } elsif ($authuser ne 'root@pam') {
1325 die "only root can delete '$opt' config for real devices\n";
1327 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1328 PVE
::QemuConfig-
>write_config($vmid, $conf);
1330 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1331 PVE
::QemuConfig-
>write_config($vmid, $conf);
1335 foreach my $opt (keys %$param) { # add/change
1336 $modified->{$opt} = 1;
1337 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1338 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1340 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1342 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1343 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1344 # FIXME: cloudinit: CDROM or Disk?
1345 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1346 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1348 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1350 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1351 if defined($conf->{pending
}->{$opt});
1353 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1354 } elsif ($opt =~ m/^serial\d+/) {
1355 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1356 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1357 } elsif ($authuser ne 'root@pam') {
1358 die "only root can modify '$opt' config for real devices\n";
1360 $conf->{pending
}->{$opt} = $param->{$opt};
1361 } elsif ($opt =~ m/^usb\d+/) {
1362 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1363 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1364 } elsif ($authuser ne 'root@pam') {
1365 die "only root can modify '$opt' config for real devices\n";
1367 $conf->{pending
}->{$opt} = $param->{$opt};
1369 $conf->{pending
}->{$opt} = $param->{$opt};
1371 if ($opt eq 'boot') {
1372 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1373 if ($new_bootcfg->{order
}) {
1374 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1375 for my $dev (@devs) {
1376 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1377 my $deleted = grep {$_ eq $dev} @delete;
1378 die "invalid bootorder: device '$dev' does not exist'\n"
1379 if !$exists || $deleted;
1382 # remove legacy boot order settings if new one set
1383 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1384 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1385 if $conf->{bootdisk
};
1389 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1390 PVE
::QemuConfig-
>write_config($vmid, $conf);
1393 # remove pending changes when nothing changed
1394 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1395 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1396 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1398 return if !scalar(keys %{$conf->{pending
}});
1400 my $running = PVE
::QemuServer
::check_running
($vmid);
1402 # apply pending changes
1404 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1408 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1410 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1412 raise_param_exc
($errors) if scalar(keys %$errors);
1421 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1423 if ($background_delay) {
1425 # Note: It would be better to do that in the Event based HTTPServer
1426 # to avoid blocking call to sleep.
1428 my $end_time = time() + $background_delay;
1430 my $task = PVE
::Tools
::upid_decode
($upid);
1433 while (time() < $end_time) {
1434 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1436 sleep(1); # this gets interrupted when child process ends
1440 my $status = PVE
::Tools
::upid_read_status
($upid);
1441 return if $status eq 'OK';
1450 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1453 my $vm_config_perm_list = [
1458 'VM.Config.Network',
1460 'VM.Config.Options',
1461 'VM.Config.Cloudinit',
1464 __PACKAGE__-
>register_method({
1465 name
=> 'update_vm_async',
1466 path
=> '{vmid}/config',
1470 description
=> "Set virtual machine options (asynchrounous API).",
1472 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1475 additionalProperties
=> 0,
1476 properties
=> PVE
::QemuServer
::json_config_properties
(
1478 node
=> get_standard_option
('pve-node'),
1479 vmid
=> get_standard_option
('pve-vmid'),
1480 skiplock
=> get_standard_option
('skiplock'),
1482 type
=> 'string', format
=> 'pve-configid-list',
1483 description
=> "A list of settings you want to delete.",
1487 type
=> 'string', format
=> 'pve-configid-list',
1488 description
=> "Revert a pending change.",
1493 description
=> $opt_force_description,
1495 requires
=> 'delete',
1499 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1503 background_delay
=> {
1505 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1516 code
=> $update_vm_api,
1519 __PACKAGE__-
>register_method({
1520 name
=> 'update_vm',
1521 path
=> '{vmid}/config',
1525 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1527 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1530 additionalProperties
=> 0,
1531 properties
=> PVE
::QemuServer
::json_config_properties
(
1533 node
=> get_standard_option
('pve-node'),
1534 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1535 skiplock
=> get_standard_option
('skiplock'),
1537 type
=> 'string', format
=> 'pve-configid-list',
1538 description
=> "A list of settings you want to delete.",
1542 type
=> 'string', format
=> 'pve-configid-list',
1543 description
=> "Revert a pending change.",
1548 description
=> $opt_force_description,
1550 requires
=> 'delete',
1554 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1560 returns
=> { type
=> 'null' },
1563 &$update_vm_api($param, 1);
1568 __PACKAGE__-
>register_method({
1569 name
=> 'destroy_vm',
1574 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1575 ." and firewall rules",
1577 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1580 additionalProperties
=> 0,
1582 node
=> get_standard_option
('pve-node'),
1583 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1584 skiplock
=> get_standard_option
('skiplock'),
1587 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1590 'destroy-unreferenced-disks' => {
1592 description
=> "If set, destroy additionally all disks not referenced in the config"
1593 ." but with a matching VMID from all enabled storages.",
1595 default => 1, # FIXME: replace to false in PVE 7.0, this is dangerous!
1605 my $rpcenv = PVE
::RPCEnvironment
::get
();
1606 my $authuser = $rpcenv->get_user();
1607 my $vmid = $param->{vmid
};
1609 my $skiplock = $param->{skiplock
};
1610 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1611 if $skiplock && $authuser ne 'root@pam';
1613 my $early_checks = sub {
1615 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1616 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1618 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1620 if (!$param->{purge
}) {
1621 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1623 # don't allow destroy if with replication jobs but no purge param
1624 my $repl_conf = PVE
::ReplicationConfig-
>new();
1625 $repl_conf->check_for_existing_jobs($vmid);
1628 die "VM $vmid is running - destroy failed\n"
1629 if PVE
::QemuServer
::check_running
($vmid);
1639 my $storecfg = PVE
::Storage
::config
();
1641 syslog
('info', "destroy VM $vmid: $upid\n");
1642 PVE
::QemuConfig-
>lock_config($vmid, sub {
1643 # repeat, config might have changed
1644 my $ha_managed = $early_checks->();
1646 # FIXME: drop fallback to true with 7.0, to dangerous for default
1647 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'} // 1;
1649 PVE
::QemuServer
::destroy_vm
(
1652 $skiplock, { lock => 'destroyed' },
1653 $purge_unreferenced,
1656 PVE
::AccessControl
::remove_vm_access
($vmid);
1657 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1658 if ($param->{purge
}) {
1659 print "purging VM $vmid from related configurations..\n";
1660 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1661 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1664 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1665 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1669 # only now remove the zombie config, else we can have reuse race
1670 PVE
::QemuConfig-
>destroy_config($vmid);
1674 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1677 __PACKAGE__-
>register_method({
1679 path
=> '{vmid}/unlink',
1683 description
=> "Unlink/delete disk images.",
1685 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1688 additionalProperties
=> 0,
1690 node
=> get_standard_option
('pve-node'),
1691 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1693 type
=> 'string', format
=> 'pve-configid-list',
1694 description
=> "A list of disk IDs you want to delete.",
1698 description
=> $opt_force_description,
1703 returns
=> { type
=> 'null'},
1707 $param->{delete} = extract_param
($param, 'idlist');
1709 __PACKAGE__-
>update_vm($param);
1714 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1715 my $gen_rand_chars = sub {
1718 die "invalid length $length" if $length < 1;
1720 my $min = ord('!'); # first printable ascii
1722 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1723 die "failed to generate random bytes!\n"
1726 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1733 __PACKAGE__-
>register_method({
1735 path
=> '{vmid}/vncproxy',
1739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1741 description
=> "Creates a TCP VNC proxy connections.",
1743 additionalProperties
=> 0,
1745 node
=> get_standard_option
('pve-node'),
1746 vmid
=> get_standard_option
('pve-vmid'),
1750 description
=> "starts websockify instead of vncproxy",
1752 'generate-password' => {
1756 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1761 additionalProperties
=> 0,
1763 user
=> { type
=> 'string' },
1764 ticket
=> { type
=> 'string' },
1767 description
=> "Returned if requested with 'generate-password' param."
1768 ." Consists of printable ASCII characters ('!' .. '~').",
1771 cert
=> { type
=> 'string' },
1772 port
=> { type
=> 'integer' },
1773 upid
=> { type
=> 'string' },
1779 my $rpcenv = PVE
::RPCEnvironment
::get
();
1781 my $authuser = $rpcenv->get_user();
1783 my $vmid = $param->{vmid
};
1784 my $node = $param->{node
};
1785 my $websocket = $param->{websocket
};
1787 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1791 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1792 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1795 my $authpath = "/vms/$vmid";
1797 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1798 my $password = $ticket;
1799 if ($param->{'generate-password'}) {
1800 $password = $gen_rand_chars->(8);
1803 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1809 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1810 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1811 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1812 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1813 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1815 $family = PVE
::Tools
::get_host_address_family
($node);
1818 my $port = PVE
::Tools
::next_vnc_port
($family);
1825 syslog
('info', "starting vnc proxy $upid\n");
1829 if (defined($serial)) {
1831 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1833 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1834 '-timeout', $timeout, '-authpath', $authpath,
1835 '-perm', 'Sys.Console'];
1837 if ($param->{websocket
}) {
1838 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1839 push @$cmd, '-notls', '-listen', 'localhost';
1842 push @$cmd, '-c', @$remcmd, @$termcmd;
1844 PVE
::Tools
::run_command
($cmd);
1848 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1850 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1852 my $sock = IO
::Socket
::IP-
>new(
1857 GetAddrInfoFlags
=> 0,
1858 ) or die "failed to create socket: $!\n";
1859 # Inside the worker we shouldn't have any previous alarms
1860 # running anyway...:
1862 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1864 accept(my $cli, $sock) or die "connection failed: $!\n";
1867 if (PVE
::Tools
::run_command
($cmd,
1868 output
=> '>&'.fileno($cli),
1869 input
=> '<&'.fileno($cli),
1872 die "Failed to run vncproxy.\n";
1879 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1881 PVE
::Tools
::wait_for_vnc_port
($port);
1890 $res->{password
} = $password if $param->{'generate-password'};
1895 __PACKAGE__-
>register_method({
1896 name
=> 'termproxy',
1897 path
=> '{vmid}/termproxy',
1901 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1903 description
=> "Creates a TCP proxy connections.",
1905 additionalProperties
=> 0,
1907 node
=> get_standard_option
('pve-node'),
1908 vmid
=> get_standard_option
('pve-vmid'),
1912 enum
=> [qw(serial0 serial1 serial2 serial3)],
1913 description
=> "opens a serial terminal (defaults to display)",
1918 additionalProperties
=> 0,
1920 user
=> { type
=> 'string' },
1921 ticket
=> { type
=> 'string' },
1922 port
=> { type
=> 'integer' },
1923 upid
=> { type
=> 'string' },
1929 my $rpcenv = PVE
::RPCEnvironment
::get
();
1931 my $authuser = $rpcenv->get_user();
1933 my $vmid = $param->{vmid
};
1934 my $node = $param->{node
};
1935 my $serial = $param->{serial
};
1937 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1939 if (!defined($serial)) {
1941 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1942 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1946 my $authpath = "/vms/$vmid";
1948 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1953 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1954 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1955 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1956 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1957 push @$remcmd, '--';
1959 $family = PVE
::Tools
::get_host_address_family
($node);
1962 my $port = PVE
::Tools
::next_vnc_port
($family);
1964 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1965 push @$termcmd, '-iface', $serial if $serial;
1970 syslog
('info', "starting qemu termproxy $upid\n");
1972 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1973 '--perm', 'VM.Console', '--'];
1974 push @$cmd, @$remcmd, @$termcmd;
1976 PVE
::Tools
::run_command
($cmd);
1979 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1981 PVE
::Tools
::wait_for_vnc_port
($port);
1991 __PACKAGE__-
>register_method({
1992 name
=> 'vncwebsocket',
1993 path
=> '{vmid}/vncwebsocket',
1996 description
=> "You also need to pass a valid ticket (vncticket).",
1997 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1999 description
=> "Opens a weksocket for VNC traffic.",
2001 additionalProperties
=> 0,
2003 node
=> get_standard_option
('pve-node'),
2004 vmid
=> get_standard_option
('pve-vmid'),
2006 description
=> "Ticket from previous call to vncproxy.",
2011 description
=> "Port number returned by previous vncproxy call.",
2021 port
=> { type
=> 'string' },
2027 my $rpcenv = PVE
::RPCEnvironment
::get
();
2029 my $authuser = $rpcenv->get_user();
2031 my $vmid = $param->{vmid
};
2032 my $node = $param->{node
};
2034 my $authpath = "/vms/$vmid";
2036 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2038 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2040 # Note: VNC ports are acessible from outside, so we do not gain any
2041 # security if we verify that $param->{port} belongs to VM $vmid. This
2042 # check is done by verifying the VNC ticket (inside VNC protocol).
2044 my $port = $param->{port
};
2046 return { port
=> $port };
2049 __PACKAGE__-
>register_method({
2050 name
=> 'spiceproxy',
2051 path
=> '{vmid}/spiceproxy',
2056 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2058 description
=> "Returns a SPICE configuration to connect to the VM.",
2060 additionalProperties
=> 0,
2062 node
=> get_standard_option
('pve-node'),
2063 vmid
=> get_standard_option
('pve-vmid'),
2064 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2067 returns
=> get_standard_option
('remote-viewer-config'),
2071 my $rpcenv = PVE
::RPCEnvironment
::get
();
2073 my $authuser = $rpcenv->get_user();
2075 my $vmid = $param->{vmid
};
2076 my $node = $param->{node
};
2077 my $proxy = $param->{proxy
};
2079 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2080 my $title = "VM $vmid";
2081 $title .= " - ". $conf->{name
} if $conf->{name
};
2083 my $port = PVE
::QemuServer
::spice_port
($vmid);
2085 my ($ticket, undef, $remote_viewer_config) =
2086 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2088 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2089 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2091 return $remote_viewer_config;
2094 __PACKAGE__-
>register_method({
2096 path
=> '{vmid}/status',
2099 description
=> "Directory index",
2104 additionalProperties
=> 0,
2106 node
=> get_standard_option
('pve-node'),
2107 vmid
=> get_standard_option
('pve-vmid'),
2115 subdir
=> { type
=> 'string' },
2118 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2124 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2127 { subdir
=> 'current' },
2128 { subdir
=> 'start' },
2129 { subdir
=> 'stop' },
2130 { subdir
=> 'reset' },
2131 { subdir
=> 'shutdown' },
2132 { subdir
=> 'suspend' },
2133 { subdir
=> 'reboot' },
2139 __PACKAGE__-
>register_method({
2140 name
=> 'vm_status',
2141 path
=> '{vmid}/status/current',
2144 protected
=> 1, # qemu pid files are only readable by root
2145 description
=> "Get virtual machine status.",
2147 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2150 additionalProperties
=> 0,
2152 node
=> get_standard_option
('pve-node'),
2153 vmid
=> get_standard_option
('pve-vmid'),
2159 %$PVE::QemuServer
::vmstatus_return_properties
,
2161 description
=> "HA manager service status.",
2165 description
=> "Qemu VGA configuration supports spice.",
2170 description
=> "Qemu GuestAgent enabled in config.",
2180 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2182 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2183 my $status = $vmstatus->{$param->{vmid
}};
2185 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2187 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2188 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2193 __PACKAGE__-
>register_method({
2195 path
=> '{vmid}/status/start',
2199 description
=> "Start virtual machine.",
2201 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2204 additionalProperties
=> 0,
2206 node
=> get_standard_option
('pve-node'),
2207 vmid
=> get_standard_option
('pve-vmid',
2208 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2209 skiplock
=> get_standard_option
('skiplock'),
2210 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2211 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2214 enum
=> ['secure', 'insecure'],
2215 description
=> "Migration traffic is encrypted using an SSH " .
2216 "tunnel by default. On secure, completely private networks " .
2217 "this can be disabled to increase performance.",
2220 migration_network
=> {
2221 type
=> 'string', format
=> 'CIDR',
2222 description
=> "CIDR of the (sub) network that is used for migration.",
2225 machine
=> get_standard_option
('pve-qemu-machine'),
2227 description
=> "Override QEMU's -cpu argument with the given string.",
2231 targetstorage
=> get_standard_option
('pve-targetstorage'),
2233 description
=> "Wait maximal timeout seconds.",
2236 default => 'max(30, vm memory in GiB)',
2247 my $rpcenv = PVE
::RPCEnvironment
::get
();
2248 my $authuser = $rpcenv->get_user();
2250 my $node = extract_param
($param, 'node');
2251 my $vmid = extract_param
($param, 'vmid');
2252 my $timeout = extract_param
($param, 'timeout');
2254 my $machine = extract_param
($param, 'machine');
2255 my $force_cpu = extract_param
($param, 'force-cpu');
2257 my $get_root_param = sub {
2258 my $value = extract_param
($param, $_[0]);
2259 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2260 if $value && $authuser ne 'root@pam';
2264 my $stateuri = $get_root_param->('stateuri');
2265 my $skiplock = $get_root_param->('skiplock');
2266 my $migratedfrom = $get_root_param->('migratedfrom');
2267 my $migration_type = $get_root_param->('migration_type');
2268 my $migration_network = $get_root_param->('migration_network');
2269 my $targetstorage = $get_root_param->('targetstorage');
2273 if ($targetstorage) {
2274 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2276 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2277 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2281 # read spice ticket from STDIN
2283 my $nbd_protocol_version = 0;
2284 my $replicated_volumes = {};
2285 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2286 while (defined(my $line = <STDIN
>)) {
2288 if ($line =~ m/^spice_ticket: (.+)$/) {
2290 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2291 $nbd_protocol_version = $1;
2292 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2293 $replicated_volumes->{$1} = 1;
2295 # fallback for old source node
2296 $spice_ticket = $line;
2301 PVE
::Cluster
::check_cfs_quorum
();
2303 my $storecfg = PVE
::Storage
::config
();
2305 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2309 print "Requesting HA start for VM $vmid\n";
2311 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2312 PVE
::Tools
::run_command
($cmd);
2316 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2323 syslog
('info', "start VM $vmid: $upid\n");
2325 my $migrate_opts = {
2326 migratedfrom
=> $migratedfrom,
2327 spice_ticket
=> $spice_ticket,
2328 network
=> $migration_network,
2329 type
=> $migration_type,
2330 storagemap
=> $storagemap,
2331 nbd_proto_version
=> $nbd_protocol_version,
2332 replicated_volumes
=> $replicated_volumes,
2336 statefile
=> $stateuri,
2337 skiplock
=> $skiplock,
2338 forcemachine
=> $machine,
2339 timeout
=> $timeout,
2340 forcecpu
=> $force_cpu,
2343 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2347 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2351 __PACKAGE__-
>register_method({
2353 path
=> '{vmid}/status/stop',
2357 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2358 "is akin to pulling the power plug of a running computer and may damage the VM data",
2360 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2363 additionalProperties
=> 0,
2365 node
=> get_standard_option
('pve-node'),
2366 vmid
=> get_standard_option
('pve-vmid',
2367 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2368 skiplock
=> get_standard_option
('skiplock'),
2369 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2371 description
=> "Wait maximal timeout seconds.",
2377 description
=> "Do not deactivate storage volumes.",
2390 my $rpcenv = PVE
::RPCEnvironment
::get
();
2391 my $authuser = $rpcenv->get_user();
2393 my $node = extract_param
($param, 'node');
2394 my $vmid = extract_param
($param, 'vmid');
2396 my $skiplock = extract_param
($param, 'skiplock');
2397 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2398 if $skiplock && $authuser ne 'root@pam';
2400 my $keepActive = extract_param
($param, 'keepActive');
2401 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2402 if $keepActive && $authuser ne 'root@pam';
2404 my $migratedfrom = extract_param
($param, 'migratedfrom');
2405 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2406 if $migratedfrom && $authuser ne 'root@pam';
2409 my $storecfg = PVE
::Storage
::config
();
2411 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2416 print "Requesting HA stop for VM $vmid\n";
2418 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2419 PVE
::Tools
::run_command
($cmd);
2423 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2429 syslog
('info', "stop VM $vmid: $upid\n");
2431 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2432 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2436 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2440 __PACKAGE__-
>register_method({
2442 path
=> '{vmid}/status/reset',
2446 description
=> "Reset virtual machine.",
2448 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2451 additionalProperties
=> 0,
2453 node
=> get_standard_option
('pve-node'),
2454 vmid
=> get_standard_option
('pve-vmid',
2455 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2456 skiplock
=> get_standard_option
('skiplock'),
2465 my $rpcenv = PVE
::RPCEnvironment
::get
();
2467 my $authuser = $rpcenv->get_user();
2469 my $node = extract_param
($param, 'node');
2471 my $vmid = extract_param
($param, 'vmid');
2473 my $skiplock = extract_param
($param, 'skiplock');
2474 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2475 if $skiplock && $authuser ne 'root@pam';
2477 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2482 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2487 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2490 __PACKAGE__-
>register_method({
2491 name
=> 'vm_shutdown',
2492 path
=> '{vmid}/status/shutdown',
2496 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2497 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2499 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2502 additionalProperties
=> 0,
2504 node
=> get_standard_option
('pve-node'),
2505 vmid
=> get_standard_option
('pve-vmid',
2506 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2507 skiplock
=> get_standard_option
('skiplock'),
2509 description
=> "Wait maximal timeout seconds.",
2515 description
=> "Make sure the VM stops.",
2521 description
=> "Do not deactivate storage volumes.",
2534 my $rpcenv = PVE
::RPCEnvironment
::get
();
2535 my $authuser = $rpcenv->get_user();
2537 my $node = extract_param
($param, 'node');
2538 my $vmid = extract_param
($param, 'vmid');
2540 my $skiplock = extract_param
($param, 'skiplock');
2541 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2542 if $skiplock && $authuser ne 'root@pam';
2544 my $keepActive = extract_param
($param, 'keepActive');
2545 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2546 if $keepActive && $authuser ne 'root@pam';
2548 my $storecfg = PVE
::Storage
::config
();
2552 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2553 # otherwise, we will infer a shutdown command, but run into the timeout,
2554 # then when the vm is resumed, it will instantly shutdown
2556 # checking the qmp status here to get feedback to the gui/cli/api
2557 # and the status query should not take too long
2558 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2559 if ($param->{forceStop
}) {
2560 warn "VM is paused - stop instead of shutdown\n";
2563 die "VM is paused - cannot shutdown\n";
2567 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2569 my $timeout = $param->{timeout
} // 60;
2573 print "Requesting HA stop for VM $vmid\n";
2575 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2576 PVE
::Tools
::run_command
($cmd);
2580 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2587 syslog
('info', "shutdown VM $vmid: $upid\n");
2589 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2590 $shutdown, $param->{forceStop
}, $keepActive);
2594 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2598 __PACKAGE__-
>register_method({
2599 name
=> 'vm_reboot',
2600 path
=> '{vmid}/status/reboot',
2604 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2606 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2609 additionalProperties
=> 0,
2611 node
=> get_standard_option
('pve-node'),
2612 vmid
=> get_standard_option
('pve-vmid',
2613 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2615 description
=> "Wait maximal timeout seconds for the shutdown.",
2628 my $rpcenv = PVE
::RPCEnvironment
::get
();
2629 my $authuser = $rpcenv->get_user();
2631 my $node = extract_param
($param, 'node');
2632 my $vmid = extract_param
($param, 'vmid');
2634 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2636 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2641 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2642 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2646 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2649 __PACKAGE__-
>register_method({
2650 name
=> 'vm_suspend',
2651 path
=> '{vmid}/status/suspend',
2655 description
=> "Suspend virtual machine.",
2657 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2658 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2659 " on the storage for the vmstate.",
2660 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2663 additionalProperties
=> 0,
2665 node
=> get_standard_option
('pve-node'),
2666 vmid
=> get_standard_option
('pve-vmid',
2667 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2668 skiplock
=> get_standard_option
('skiplock'),
2673 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2675 statestorage
=> get_standard_option
('pve-storage-id', {
2676 description
=> "The storage for the VM state",
2677 requires
=> 'todisk',
2679 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2689 my $rpcenv = PVE
::RPCEnvironment
::get
();
2690 my $authuser = $rpcenv->get_user();
2692 my $node = extract_param
($param, 'node');
2693 my $vmid = extract_param
($param, 'vmid');
2695 my $todisk = extract_param
($param, 'todisk') // 0;
2697 my $statestorage = extract_param
($param, 'statestorage');
2699 my $skiplock = extract_param
($param, 'skiplock');
2700 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2701 if $skiplock && $authuser ne 'root@pam';
2703 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2705 die "Cannot suspend HA managed VM to disk\n"
2706 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2708 # early check for storage permission, for better user feedback
2710 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2712 if (!$statestorage) {
2713 # get statestorage from config if none is given
2714 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2715 my $storecfg = PVE
::Storage
::config
();
2716 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2719 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2725 syslog
('info', "suspend VM $vmid: $upid\n");
2727 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2732 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2733 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2736 __PACKAGE__-
>register_method({
2737 name
=> 'vm_resume',
2738 path
=> '{vmid}/status/resume',
2742 description
=> "Resume virtual machine.",
2744 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2747 additionalProperties
=> 0,
2749 node
=> get_standard_option
('pve-node'),
2750 vmid
=> get_standard_option
('pve-vmid',
2751 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2752 skiplock
=> get_standard_option
('skiplock'),
2753 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2763 my $rpcenv = PVE
::RPCEnvironment
::get
();
2765 my $authuser = $rpcenv->get_user();
2767 my $node = extract_param
($param, 'node');
2769 my $vmid = extract_param
($param, 'vmid');
2771 my $skiplock = extract_param
($param, 'skiplock');
2772 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2773 if $skiplock && $authuser ne 'root@pam';
2775 my $nocheck = extract_param
($param, 'nocheck');
2776 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2777 if $nocheck && $authuser ne 'root@pam';
2779 my $to_disk_suspended;
2781 PVE
::QemuConfig-
>lock_config($vmid, sub {
2782 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2783 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2787 die "VM $vmid not running\n"
2788 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2793 syslog
('info', "resume VM $vmid: $upid\n");
2795 if (!$to_disk_suspended) {
2796 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2798 my $storecfg = PVE
::Storage
::config
();
2799 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2805 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2808 __PACKAGE__-
>register_method({
2809 name
=> 'vm_sendkey',
2810 path
=> '{vmid}/sendkey',
2814 description
=> "Send key event to virtual machine.",
2816 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2819 additionalProperties
=> 0,
2821 node
=> get_standard_option
('pve-node'),
2822 vmid
=> get_standard_option
('pve-vmid',
2823 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2824 skiplock
=> get_standard_option
('skiplock'),
2826 description
=> "The key (qemu monitor encoding).",
2831 returns
=> { type
=> 'null'},
2835 my $rpcenv = PVE
::RPCEnvironment
::get
();
2837 my $authuser = $rpcenv->get_user();
2839 my $node = extract_param
($param, 'node');
2841 my $vmid = extract_param
($param, 'vmid');
2843 my $skiplock = extract_param
($param, 'skiplock');
2844 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2845 if $skiplock && $authuser ne 'root@pam';
2847 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2852 __PACKAGE__-
>register_method({
2853 name
=> 'vm_feature',
2854 path
=> '{vmid}/feature',
2858 description
=> "Check if feature for virtual machine is available.",
2860 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2863 additionalProperties
=> 0,
2865 node
=> get_standard_option
('pve-node'),
2866 vmid
=> get_standard_option
('pve-vmid'),
2868 description
=> "Feature to check.",
2870 enum
=> [ 'snapshot', 'clone', 'copy' ],
2872 snapname
=> get_standard_option
('pve-snapshot-name', {
2880 hasFeature
=> { type
=> 'boolean' },
2883 items
=> { type
=> 'string' },
2890 my $node = extract_param
($param, 'node');
2892 my $vmid = extract_param
($param, 'vmid');
2894 my $snapname = extract_param
($param, 'snapname');
2896 my $feature = extract_param
($param, 'feature');
2898 my $running = PVE
::QemuServer
::check_running
($vmid);
2900 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2903 my $snap = $conf->{snapshots
}->{$snapname};
2904 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2907 my $storecfg = PVE
::Storage
::config
();
2909 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2910 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2913 hasFeature
=> $hasFeature,
2914 nodes
=> [ keys %$nodelist ],
2918 __PACKAGE__-
>register_method({
2920 path
=> '{vmid}/clone',
2924 description
=> "Create a copy of virtual machine/template.",
2926 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2927 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2928 "'Datastore.AllocateSpace' on any used storage.",
2931 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2933 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2934 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2939 additionalProperties
=> 0,
2941 node
=> get_standard_option
('pve-node'),
2942 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2943 newid
=> get_standard_option
('pve-vmid', {
2944 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2945 description
=> 'VMID for the clone.' }),
2948 type
=> 'string', format
=> 'dns-name',
2949 description
=> "Set a name for the new VM.",
2954 description
=> "Description for the new VM.",
2958 type
=> 'string', format
=> 'pve-poolid',
2959 description
=> "Add the new VM to the specified pool.",
2961 snapname
=> get_standard_option
('pve-snapshot-name', {
2964 storage
=> get_standard_option
('pve-storage-id', {
2965 description
=> "Target storage for full clone.",
2969 description
=> "Target format for file storage. Only valid for full clone.",
2972 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2977 description
=> "Create a full copy of all disks. This is always done when " .
2978 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2980 target
=> get_standard_option
('pve-node', {
2981 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2985 description
=> "Override I/O bandwidth limit (in KiB/s).",
2989 default => 'clone limit from datacenter or storage config',
2999 my $rpcenv = PVE
::RPCEnvironment
::get
();
3000 my $authuser = $rpcenv->get_user();
3002 my $node = extract_param
($param, 'node');
3003 my $vmid = extract_param
($param, 'vmid');
3004 my $newid = extract_param
($param, 'newid');
3005 my $pool = extract_param
($param, 'pool');
3006 $rpcenv->check_pool_exist($pool) if defined($pool);
3008 my $snapname = extract_param
($param, 'snapname');
3009 my $storage = extract_param
($param, 'storage');
3010 my $format = extract_param
($param, 'format');
3011 my $target = extract_param
($param, 'target');
3013 my $localnode = PVE
::INotify
::nodename
();
3015 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3019 PVE
::Cluster
::check_node_exists
($target) if $target;
3021 my $storecfg = PVE
::Storage
::config
();
3024 # check if storage is enabled on local node
3025 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3027 # check if storage is available on target node
3028 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
3029 # clone only works if target storage is shared
3030 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3031 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3035 PVE
::Cluster
::check_cfs_quorum
();
3037 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3040 # do all tests after lock but before forking worker - if possible
3042 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3043 PVE
::QemuConfig-
>check_lock($conf);
3045 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3046 die "unexpected state change\n" if $verify_running != $running;
3048 die "snapshot '$snapname' does not exist\n"
3049 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3051 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3053 die "parameter 'storage' not allowed for linked clones\n"
3054 if defined($storage) && !$full;
3056 die "parameter 'format' not allowed for linked clones\n"
3057 if defined($format) && !$full;
3059 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3061 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3063 die "can't clone VM to node '$target' (VM uses local storage)\n"
3064 if $target && !$sharedvm;
3066 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3067 die "unable to create VM $newid: config file already exists\n"
3070 my $newconf = { lock => 'clone' };
3075 foreach my $opt (keys %$oldconf) {
3076 my $value = $oldconf->{$opt};
3078 # do not copy snapshot related info
3079 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3080 $opt eq 'vmstate' || $opt eq 'snapstate';
3082 # no need to copy unused images, because VMID(owner) changes anyways
3083 next if $opt =~ m/^unused\d+$/;
3085 # always change MAC! address
3086 if ($opt =~ m/^net(\d+)$/) {
3087 my $net = PVE
::QemuServer
::parse_net
($value);
3088 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3089 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3090 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3091 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3092 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3093 die "unable to parse drive options for '$opt'\n" if !$drive;
3094 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3095 $newconf->{$opt} = $value; # simply copy configuration
3097 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3098 die "Full clone feature is not supported for drive '$opt'\n"
3099 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3100 $fullclone->{$opt} = 1;
3102 # not full means clone instead of copy
3103 die "Linked clone feature is not supported for drive '$opt'\n"
3104 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3106 $drives->{$opt} = $drive;
3107 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3108 push @$vollist, $drive->{file
};
3111 # copy everything else
3112 $newconf->{$opt} = $value;
3116 # auto generate a new uuid
3117 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3118 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3119 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3120 # auto generate a new vmgenid only if the option was set for template
3121 if ($newconf->{vmgenid
}) {
3122 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3125 delete $newconf->{template
};
3127 if ($param->{name
}) {
3128 $newconf->{name
} = $param->{name
};
3130 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3133 if ($param->{description
}) {
3134 $newconf->{description
} = $param->{description
};
3137 # create empty/temp config - this fails if VM already exists on other node
3138 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3139 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3144 my $newvollist = [];
3151 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3153 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3155 my $bwlimit = extract_param
($param, 'bwlimit');
3157 my $total_jobs = scalar(keys %{$drives});
3160 foreach my $opt (keys %$drives) {
3161 my $drive = $drives->{$opt};
3162 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3163 my $completion = $skipcomplete ?
'skip' : 'complete';
3165 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3166 my $storage_list = [ $src_sid ];
3167 push @$storage_list, $storage if defined($storage);
3168 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3170 my $newdrive = PVE
::QemuServer
::clone_disk
(
3189 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3191 PVE
::QemuConfig-
>write_config($newid, $newconf);
3195 delete $newconf->{lock};
3197 # do not write pending changes
3198 if (my @changes = keys %{$newconf->{pending
}}) {
3199 my $pending = join(',', @changes);
3200 warn "found pending changes for '$pending', discarding for clone\n";
3201 delete $newconf->{pending
};
3204 PVE
::QemuConfig-
>write_config($newid, $newconf);
3207 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3208 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3209 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3211 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3212 die "Failed to move config to node '$target' - rename failed: $!\n"
3213 if !rename($conffile, $newconffile);
3216 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3219 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3220 sleep 1; # some storage like rbd need to wait before release volume - really?
3222 foreach my $volid (@$newvollist) {
3223 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3227 PVE
::Firewall
::remove_vmfw_conf
($newid);
3229 unlink $conffile; # avoid races -> last thing before die
3231 die "clone failed: $err";
3237 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3239 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3242 # Aquire exclusive lock lock for $newid
3243 my $lock_target_vm = sub {
3244 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3247 # exclusive lock if VM is running - else shared lock is enough;
3249 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3251 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3255 __PACKAGE__-
>register_method({
3256 name
=> 'move_vm_disk',
3257 path
=> '{vmid}/move_disk',
3261 description
=> "Move volume to different storage.",
3263 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3265 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3266 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3270 additionalProperties
=> 0,
3272 node
=> get_standard_option
('pve-node'),
3273 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3276 description
=> "The disk you want to move.",
3277 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3279 storage
=> get_standard_option
('pve-storage-id', {
3280 description
=> "Target storage.",
3281 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3285 description
=> "Target Format.",
3286 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3291 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3297 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3302 description
=> "Override I/O bandwidth limit (in KiB/s).",
3306 default => 'move limit from datacenter or storage config',
3312 description
=> "the task ID.",
3317 my $rpcenv = PVE
::RPCEnvironment
::get
();
3318 my $authuser = $rpcenv->get_user();
3320 my $node = extract_param
($param, 'node');
3321 my $vmid = extract_param
($param, 'vmid');
3322 my $digest = extract_param
($param, 'digest');
3323 my $disk = extract_param
($param, 'disk');
3324 my $storeid = extract_param
($param, 'storage');
3325 my $format = extract_param
($param, 'format');
3327 my $storecfg = PVE
::Storage
::config
();
3329 my $updatefn = sub {
3330 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3331 PVE
::QemuConfig-
>check_lock($conf);
3333 die "VM config checksum missmatch (file change by other user?)\n"
3334 if $digest && $digest ne $conf->{digest
};
3336 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3338 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3340 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3341 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3343 my $old_volid = $drive->{file
};
3345 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3346 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3350 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3351 (!$format || !$oldfmt || $oldfmt eq $format);
3353 # this only checks snapshots because $disk is passed!
3354 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3355 die "you can't move a disk with snapshots and delete the source\n"
3356 if $snapshotted && $param->{delete};
3358 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3360 my $running = PVE
::QemuServer
::check_running
($vmid);
3362 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3365 my $newvollist = [];
3371 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3373 warn "moving disk with snapshots, snapshots will not be moved!\n"
3376 my $bwlimit = extract_param
($param, 'bwlimit');
3377 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3379 my $newdrive = PVE
::QemuServer
::clone_disk
(
3397 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3399 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3401 # convert moved disk to base if part of template
3402 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3403 if PVE
::QemuConfig-
>is_template($conf);
3405 PVE
::QemuConfig-
>write_config($vmid, $conf);
3407 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3408 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3409 eval { mon_cmd
($vmid, "guest-fstrim") };
3413 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3414 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3420 foreach my $volid (@$newvollist) {
3421 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3424 die "storage migration failed: $err";
3427 if ($param->{delete}) {
3429 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3430 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3436 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3439 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3442 my $check_vm_disks_local = sub {
3443 my ($storecfg, $vmconf, $vmid) = @_;
3445 my $local_disks = {};
3447 # add some more information to the disks e.g. cdrom
3448 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3449 my ($volid, $attr) = @_;
3451 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3453 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3454 return if $scfg->{shared
};
3456 # The shared attr here is just a special case where the vdisk
3457 # is marked as shared manually
3458 return if $attr->{shared
};
3459 return if $attr->{cdrom
} and $volid eq "none";
3461 if (exists $local_disks->{$volid}) {
3462 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3464 $local_disks->{$volid} = $attr;
3465 # ensure volid is present in case it's needed
3466 $local_disks->{$volid}->{volid
} = $volid;
3470 return $local_disks;
3473 __PACKAGE__-
>register_method({
3474 name
=> 'migrate_vm_precondition',
3475 path
=> '{vmid}/migrate',
3479 description
=> "Get preconditions for migration.",
3481 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3484 additionalProperties
=> 0,
3486 node
=> get_standard_option
('pve-node'),
3487 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3488 target
=> get_standard_option
('pve-node', {
3489 description
=> "Target node.",
3490 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3498 running
=> { type
=> 'boolean' },
3502 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3504 not_allowed_nodes
=> {
3507 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3511 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3513 local_resources
=> {
3515 description
=> "List local resources e.g. pci, usb"
3522 my $rpcenv = PVE
::RPCEnvironment
::get
();
3524 my $authuser = $rpcenv->get_user();
3526 PVE
::Cluster
::check_cfs_quorum
();
3530 my $vmid = extract_param
($param, 'vmid');
3531 my $target = extract_param
($param, 'target');
3532 my $localnode = PVE
::INotify
::nodename
();
3536 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3537 my $storecfg = PVE
::Storage
::config
();
3540 # try to detect errors early
3541 PVE
::QemuConfig-
>check_lock($vmconf);
3543 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3545 # if vm is not running, return target nodes where local storage is available
3546 # for offline migration
3547 if (!$res->{running
}) {
3548 $res->{allowed_nodes
} = [];
3549 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3550 delete $checked_nodes->{$localnode};
3552 foreach my $node (keys %$checked_nodes) {
3553 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3554 push @{$res->{allowed_nodes
}}, $node;
3558 $res->{not_allowed_nodes
} = $checked_nodes;
3562 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3563 $res->{local_disks
} = [ values %$local_disks ];;
3565 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3567 $res->{local_resources
} = $local_resources;
3574 __PACKAGE__-
>register_method({
3575 name
=> 'migrate_vm',
3576 path
=> '{vmid}/migrate',
3580 description
=> "Migrate virtual machine. Creates a new migration task.",
3582 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3585 additionalProperties
=> 0,
3587 node
=> get_standard_option
('pve-node'),
3588 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3589 target
=> get_standard_option
('pve-node', {
3590 description
=> "Target node.",
3591 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3595 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3600 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3605 enum
=> ['secure', 'insecure'],
3606 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3609 migration_network
=> {
3610 type
=> 'string', format
=> 'CIDR',
3611 description
=> "CIDR of the (sub) network that is used for migration.",
3614 "with-local-disks" => {
3616 description
=> "Enable live storage migration for local disk",
3619 targetstorage
=> get_standard_option
('pve-targetstorage', {
3620 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3623 description
=> "Override I/O bandwidth limit (in KiB/s).",
3627 default => 'migrate limit from datacenter or storage config',
3633 description
=> "the task ID.",
3638 my $rpcenv = PVE
::RPCEnvironment
::get
();
3639 my $authuser = $rpcenv->get_user();
3641 my $target = extract_param
($param, 'target');
3643 my $localnode = PVE
::INotify
::nodename
();
3644 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3646 PVE
::Cluster
::check_cfs_quorum
();
3648 PVE
::Cluster
::check_node_exists
($target);
3650 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3652 my $vmid = extract_param
($param, 'vmid');
3654 raise_param_exc
({ force
=> "Only root may use this option." })
3655 if $param->{force
} && $authuser ne 'root@pam';
3657 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3658 if $param->{migration_type
} && $authuser ne 'root@pam';
3660 # allow root only until better network permissions are available
3661 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3662 if $param->{migration_network
} && $authuser ne 'root@pam';
3665 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3667 # try to detect errors early
3669 PVE
::QemuConfig-
>check_lock($conf);
3671 if (PVE
::QemuServer
::check_running
($vmid)) {
3672 die "can't migrate running VM without --online\n" if !$param->{online
};
3674 my $repl_conf = PVE
::ReplicationConfig-
>new();
3675 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3676 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3677 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3678 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3679 "target. Use 'force' to override.\n";
3682 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3683 $param->{online
} = 0;
3686 my $storecfg = PVE
::Storage
::config
();
3688 if (my $targetstorage = $param->{targetstorage
}) {
3689 my $check_storage = sub {
3690 my ($target_sid) = @_;
3691 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3692 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3693 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3694 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3695 if !$scfg->{content
}->{images
};
3698 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3699 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3702 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3703 if !defined($storagemap->{identity
});
3705 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3706 $check_storage->($target_sid);
3709 $check_storage->($storagemap->{default})
3710 if $storagemap->{default};
3712 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3713 if $storagemap->{identity
};
3715 $param->{storagemap
} = $storagemap;
3717 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3720 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3725 print "Requesting HA migration for VM $vmid to node $target\n";
3727 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3728 PVE
::Tools
::run_command
($cmd);
3732 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3737 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3741 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3744 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3749 __PACKAGE__-
>register_method({
3751 path
=> '{vmid}/monitor',
3755 description
=> "Execute Qemu monitor commands.",
3757 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3758 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3761 additionalProperties
=> 0,
3763 node
=> get_standard_option
('pve-node'),
3764 vmid
=> get_standard_option
('pve-vmid'),
3767 description
=> "The monitor command.",
3771 returns
=> { type
=> 'string'},
3775 my $rpcenv = PVE
::RPCEnvironment
::get
();
3776 my $authuser = $rpcenv->get_user();
3779 my $command = shift;
3780 return $command =~ m/^\s*info(\s+|$)/
3781 || $command =~ m/^\s*help\s*$/;
3784 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3785 if !&$is_ro($param->{command
});
3787 my $vmid = $param->{vmid
};
3789 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3793 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3795 $res = "ERROR: $@" if $@;
3800 __PACKAGE__-
>register_method({
3801 name
=> 'resize_vm',
3802 path
=> '{vmid}/resize',
3806 description
=> "Extend volume size.",
3808 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3811 additionalProperties
=> 0,
3813 node
=> get_standard_option
('pve-node'),
3814 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3815 skiplock
=> get_standard_option
('skiplock'),
3818 description
=> "The disk you want to resize.",
3819 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3823 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3824 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.",
3828 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3834 returns
=> { type
=> 'null'},
3838 my $rpcenv = PVE
::RPCEnvironment
::get
();
3840 my $authuser = $rpcenv->get_user();
3842 my $node = extract_param
($param, 'node');
3844 my $vmid = extract_param
($param, 'vmid');
3846 my $digest = extract_param
($param, 'digest');
3848 my $disk = extract_param
($param, 'disk');
3850 my $sizestr = extract_param
($param, 'size');
3852 my $skiplock = extract_param
($param, 'skiplock');
3853 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3854 if $skiplock && $authuser ne 'root@pam';
3856 my $storecfg = PVE
::Storage
::config
();
3858 my $updatefn = sub {
3860 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3862 die "checksum missmatch (file change by other user?)\n"
3863 if $digest && $digest ne $conf->{digest
};
3864 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3866 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3868 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3870 my (undef, undef, undef, undef, undef, undef, $format) =
3871 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3873 die "can't resize volume: $disk if snapshot exists\n"
3874 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3876 my $volid = $drive->{file
};
3878 die "disk '$disk' has no associated volume\n" if !$volid;
3880 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3882 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3884 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3886 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3887 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3889 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3891 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3892 my ($ext, $newsize, $unit) = ($1, $2, $4);
3895 $newsize = $newsize * 1024;
3896 } elsif ($unit eq 'M') {
3897 $newsize = $newsize * 1024 * 1024;
3898 } elsif ($unit eq 'G') {
3899 $newsize = $newsize * 1024 * 1024 * 1024;
3900 } elsif ($unit eq 'T') {
3901 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3904 $newsize += $size if $ext;
3905 $newsize = int($newsize);
3907 die "shrinking disks is not supported\n" if $newsize < $size;
3909 return if $size == $newsize;
3911 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3913 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3915 $drive->{size
} = $newsize;
3916 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3918 PVE
::QemuConfig-
>write_config($vmid, $conf);
3921 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3925 __PACKAGE__-
>register_method({
3926 name
=> 'snapshot_list',
3927 path
=> '{vmid}/snapshot',
3929 description
=> "List all snapshots.",
3931 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3934 protected
=> 1, # qemu pid files are only readable by root
3936 additionalProperties
=> 0,
3938 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3939 node
=> get_standard_option
('pve-node'),
3948 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3952 description
=> "Snapshot includes RAM.",
3957 description
=> "Snapshot description.",
3961 description
=> "Snapshot creation time",
3963 renderer
=> 'timestamp',
3967 description
=> "Parent snapshot identifier.",
3973 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3978 my $vmid = $param->{vmid
};
3980 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3981 my $snaphash = $conf->{snapshots
} || {};
3985 foreach my $name (keys %$snaphash) {
3986 my $d = $snaphash->{$name};
3989 snaptime
=> $d->{snaptime
} || 0,
3990 vmstate
=> $d->{vmstate
} ?
1 : 0,
3991 description
=> $d->{description
} || '',
3993 $item->{parent
} = $d->{parent
} if $d->{parent
};
3994 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3998 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4001 digest
=> $conf->{digest
},
4002 running
=> $running,
4003 description
=> "You are here!",
4005 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4007 push @$res, $current;
4012 __PACKAGE__-
>register_method({
4014 path
=> '{vmid}/snapshot',
4018 description
=> "Snapshot a VM.",
4020 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4023 additionalProperties
=> 0,
4025 node
=> get_standard_option
('pve-node'),
4026 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4027 snapname
=> get_standard_option
('pve-snapshot-name'),
4031 description
=> "Save the vmstate",
4036 description
=> "A textual description or comment.",
4042 description
=> "the task ID.",
4047 my $rpcenv = PVE
::RPCEnvironment
::get
();
4049 my $authuser = $rpcenv->get_user();
4051 my $node = extract_param
($param, 'node');
4053 my $vmid = extract_param
($param, 'vmid');
4055 my $snapname = extract_param
($param, 'snapname');
4057 die "unable to use snapshot name 'current' (reserved name)\n"
4058 if $snapname eq 'current';
4060 die "unable to use snapshot name 'pending' (reserved name)\n"
4061 if lc($snapname) eq 'pending';
4064 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4065 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4066 $param->{description
});
4069 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4072 __PACKAGE__-
>register_method({
4073 name
=> 'snapshot_cmd_idx',
4074 path
=> '{vmid}/snapshot/{snapname}',
4081 additionalProperties
=> 0,
4083 vmid
=> get_standard_option
('pve-vmid'),
4084 node
=> get_standard_option
('pve-node'),
4085 snapname
=> get_standard_option
('pve-snapshot-name'),
4094 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4101 push @$res, { cmd
=> 'rollback' };
4102 push @$res, { cmd
=> 'config' };
4107 __PACKAGE__-
>register_method({
4108 name
=> 'update_snapshot_config',
4109 path
=> '{vmid}/snapshot/{snapname}/config',
4113 description
=> "Update snapshot metadata.",
4115 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4118 additionalProperties
=> 0,
4120 node
=> get_standard_option
('pve-node'),
4121 vmid
=> get_standard_option
('pve-vmid'),
4122 snapname
=> get_standard_option
('pve-snapshot-name'),
4126 description
=> "A textual description or comment.",
4130 returns
=> { type
=> 'null' },
4134 my $rpcenv = PVE
::RPCEnvironment
::get
();
4136 my $authuser = $rpcenv->get_user();
4138 my $vmid = extract_param
($param, 'vmid');
4140 my $snapname = extract_param
($param, 'snapname');
4142 return if !defined($param->{description
});
4144 my $updatefn = sub {
4146 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4148 PVE
::QemuConfig-
>check_lock($conf);
4150 my $snap = $conf->{snapshots
}->{$snapname};
4152 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4154 $snap->{description
} = $param->{description
} if defined($param->{description
});
4156 PVE
::QemuConfig-
>write_config($vmid, $conf);
4159 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4164 __PACKAGE__-
>register_method({
4165 name
=> 'get_snapshot_config',
4166 path
=> '{vmid}/snapshot/{snapname}/config',
4169 description
=> "Get snapshot configuration",
4171 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4174 additionalProperties
=> 0,
4176 node
=> get_standard_option
('pve-node'),
4177 vmid
=> get_standard_option
('pve-vmid'),
4178 snapname
=> get_standard_option
('pve-snapshot-name'),
4181 returns
=> { type
=> "object" },
4185 my $rpcenv = PVE
::RPCEnvironment
::get
();
4187 my $authuser = $rpcenv->get_user();
4189 my $vmid = extract_param
($param, 'vmid');
4191 my $snapname = extract_param
($param, 'snapname');
4193 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4195 my $snap = $conf->{snapshots
}->{$snapname};
4197 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4202 __PACKAGE__-
>register_method({
4204 path
=> '{vmid}/snapshot/{snapname}/rollback',
4208 description
=> "Rollback VM state to specified snapshot.",
4210 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4213 additionalProperties
=> 0,
4215 node
=> get_standard_option
('pve-node'),
4216 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4217 snapname
=> get_standard_option
('pve-snapshot-name'),
4222 description
=> "the task ID.",
4227 my $rpcenv = PVE
::RPCEnvironment
::get
();
4229 my $authuser = $rpcenv->get_user();
4231 my $node = extract_param
($param, 'node');
4233 my $vmid = extract_param
($param, 'vmid');
4235 my $snapname = extract_param
($param, 'snapname');
4238 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4239 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4243 # hold migration lock, this makes sure that nobody create replication snapshots
4244 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4247 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4250 __PACKAGE__-
>register_method({
4251 name
=> 'delsnapshot',
4252 path
=> '{vmid}/snapshot/{snapname}',
4256 description
=> "Delete a VM snapshot.",
4258 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4261 additionalProperties
=> 0,
4263 node
=> get_standard_option
('pve-node'),
4264 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4265 snapname
=> get_standard_option
('pve-snapshot-name'),
4269 description
=> "For removal from config file, even if removing disk snapshots fails.",
4275 description
=> "the task ID.",
4280 my $rpcenv = PVE
::RPCEnvironment
::get
();
4282 my $authuser = $rpcenv->get_user();
4284 my $node = extract_param
($param, 'node');
4286 my $vmid = extract_param
($param, 'vmid');
4288 my $snapname = extract_param
($param, 'snapname');
4291 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4292 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4295 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4298 __PACKAGE__-
>register_method({
4300 path
=> '{vmid}/template',
4304 description
=> "Create a Template.",
4306 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4307 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4310 additionalProperties
=> 0,
4312 node
=> get_standard_option
('pve-node'),
4313 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4317 description
=> "If you want to convert only 1 disk to base image.",
4318 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4323 returns
=> { type
=> 'null'},
4327 my $rpcenv = PVE
::RPCEnvironment
::get
();
4329 my $authuser = $rpcenv->get_user();
4331 my $node = extract_param
($param, 'node');
4333 my $vmid = extract_param
($param, 'vmid');
4335 my $disk = extract_param
($param, 'disk');
4337 my $updatefn = sub {
4339 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4341 PVE
::QemuConfig-
>check_lock($conf);
4343 die "unable to create template, because VM contains snapshots\n"
4344 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4346 die "you can't convert a template to a template\n"
4347 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4349 die "you can't convert a VM to template if VM is running\n"
4350 if PVE
::QemuServer
::check_running
($vmid);
4353 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4356 $conf->{template
} = 1;
4357 PVE
::QemuConfig-
>write_config($vmid, $conf);
4359 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4362 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4366 __PACKAGE__-
>register_method({
4367 name
=> 'cloudinit_generated_config_dump',
4368 path
=> '{vmid}/cloudinit/dump',
4371 description
=> "Get automatically generated cloudinit config.",
4373 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4376 additionalProperties
=> 0,
4378 node
=> get_standard_option
('pve-node'),
4379 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4381 description
=> 'Config type.',
4383 enum
=> ['user', 'network', 'meta'],
4393 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4395 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});