1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::RPCEnvironment
;
29 use PVE
::AccessControl
;
33 use PVE
::API2
::Firewall
::VM
;
34 use PVE
::API2
::Qemu
::Agent
;
35 use PVE
::VZDump
::Plugin
;
36 use PVE
::DataCenterConfig
;
40 if (!$ENV{PVE_GENERATING_DOCS
}) {
41 require PVE
::HA
::Env
::PVE2
;
42 import PVE
::HA
::Env
::PVE2
;
43 require PVE
::HA
::Config
;
44 import PVE
::HA
::Config
;
48 use Data
::Dumper
; # fixme: remove
50 use base
qw(PVE::RESTHandler);
52 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
54 my $resolve_cdrom_alias = sub {
57 if (my $value = $param->{cdrom
}) {
58 $value .= ",media=cdrom" if $value !~ m/media=/;
59 $param->{ide2
} = $value;
60 delete $param->{cdrom
};
64 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
65 my $check_storage_access = sub {
66 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
68 PVE
::QemuConfig-
>foreach_volume($settings, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
74 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
76 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
78 } elsif ($isCDROM && ($volid eq 'cdrom')) {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
81 my ($storeid, $size) = ($2 || $default_storage, $3);
82 die "no storage ID specified (and no default storage)\n" if !$storeid;
83 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
85 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
86 if !$scfg->{content
}->{images
};
88 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
92 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
93 if defined($settings->{vmstatestorage
});
96 my $check_storage_access_clone = sub {
97 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
101 PVE
::QemuConfig-
>foreach_volume($conf, sub {
102 my ($ds, $drive) = @_;
104 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
106 my $volid = $drive->{file
};
108 return if !$volid || $volid eq 'none';
111 if ($volid eq 'cdrom') {
112 $rpcenv->check($authuser, "/", ['Sys.Console']);
114 # we simply allow access
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
121 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
122 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
123 $sharedvm = 0 if !$scfg->{shared
};
125 $sid = $storage if $storage;
126 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
130 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
131 if defined($conf->{vmstatestorage
});
136 # Note: $pool is only needed when creating a VM, because pool permissions
137 # are automatically inherited if VM already exists inside a pool.
138 my $create_disks = sub {
139 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
146 my ($ds, $disk) = @_;
148 my $volid = $disk->{file
};
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
151 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
152 delete $disk->{size
};
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
154 } elsif (defined($volname) && $volname eq 'cloudinit') {
155 $storeid = $storeid // $default_storage;
156 die "no storage ID specified (and no default storage)\n" if !$storeid;
157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
158 my $name = "vm-$vmid-cloudinit";
162 $fmt = $disk->{format
} // "qcow2";
165 $fmt = $disk->{format
} // "raw";
168 # Initial disk created with 4 MB and aligned to 4MB on regeneration
169 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
170 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
171 $disk->{file
} = $volid;
172 $disk->{media
} = 'cdrom';
173 push @$vollist, $volid;
174 delete $disk->{format
}; # no longer needed
175 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
176 } elsif ($volid =~ $NEW_DISK_RE) {
177 my ($storeid, $size) = ($2 || $default_storage, $3);
178 die "no storage ID specified (and no default storage)\n" if !$storeid;
179 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
180 my $fmt = $disk->{format
} || $defformat;
182 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
185 if ($ds eq 'efidisk0') {
186 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
188 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
190 push @$vollist, $volid;
191 $disk->{file
} = $volid;
192 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
193 delete $disk->{format
}; # no longer needed
194 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
197 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
199 my $volid_is_new = 1;
202 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
203 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
208 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
210 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
212 die "volume $volid does not exist\n" if !$size;
214 $disk->{size
} = $size;
217 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
221 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
223 # free allocated images on error
225 syslog
('err', "VM $vmid creating disks failed");
226 foreach my $volid (@$vollist) {
227 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
233 # modify vm config if everything went well
234 foreach my $ds (keys %$res) {
235 $conf->{$ds} = $res->{$ds};
241 my $check_cpu_model_access = sub {
242 my ($rpcenv, $authuser, $new, $existing) = @_;
244 return if !defined($new->{cpu
});
246 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
247 return if !$cpu || !$cpu->{cputype
}; # always allow default
248 my $cputype = $cpu->{cputype
};
250 if ($existing && $existing->{cpu
}) {
251 # changing only other settings doesn't require permissions for CPU model
252 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
253 return if $existingCpu->{cputype
} eq $cputype;
256 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
257 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
272 my $memoryoptions = {
278 my $hwtypeoptions = {
291 my $generaloptions = {
298 'migrate_downtime' => 1,
299 'migrate_speed' => 1,
312 my $vmpoweroptions = {
319 'vmstatestorage' => 1,
322 my $cloudinitoptions = {
332 my $check_vm_create_serial_perm = sub {
333 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
335 return 1 if $authuser eq 'root@pam';
337 foreach my $opt (keys %{$param}) {
338 next if $opt !~ m/^serial\d+$/;
340 if ($param->{$opt} eq 'socket') {
341 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
343 die "only root can set '$opt' config for real devices\n";
350 my $check_vm_create_usb_perm = sub {
351 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
353 return 1 if $authuser eq 'root@pam';
355 foreach my $opt (keys %{$param}) {
356 next if $opt !~ m/^usb\d+$/;
358 if ($param->{$opt} =~ m/spice/) {
359 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
361 die "only root can set '$opt' config for real devices\n";
368 my $check_vm_modify_config_perm = sub {
369 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
371 return 1 if $authuser eq 'root@pam';
373 foreach my $opt (@$key_list) {
374 # some checks (e.g., disk, serial port, usb) need to be done somewhere
375 # else, as there the permission can be value dependend
376 next if PVE
::QemuServer
::is_valid_drivename
($opt);
377 next if $opt eq 'cdrom';
378 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
381 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
382 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
383 } elsif ($memoryoptions->{$opt}) {
384 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
385 } elsif ($hwtypeoptions->{$opt}) {
386 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
387 } elsif ($generaloptions->{$opt}) {
388 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
389 # special case for startup since it changes host behaviour
390 if ($opt eq 'startup') {
391 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
393 } elsif ($vmpoweroptions->{$opt}) {
394 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
395 } elsif ($diskoptions->{$opt}) {
396 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
397 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
398 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
399 } elsif ($cloudinitoptions->{$opt}) {
400 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
401 } elsif ($opt eq 'vmstate') {
402 # the user needs Disk and PowerMgmt privileges to change the vmstate
403 # also needs privileges on the storage, that will be checked later
404 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
406 # catches hostpci\d+, args, lock, etc.
407 # new options will be checked here
408 die "only root can set '$opt' config\n";
415 __PACKAGE__-
>register_method({
419 description
=> "Virtual machine index (per node).",
421 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
425 protected
=> 1, # qemu pid files are only readable by root
427 additionalProperties
=> 0,
429 node
=> get_standard_option
('pve-node'),
433 description
=> "Determine the full status of active VMs.",
441 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
443 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
448 my $rpcenv = PVE
::RPCEnvironment
::get
();
449 my $authuser = $rpcenv->get_user();
451 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
454 foreach my $vmid (keys %$vmstatus) {
455 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
457 my $data = $vmstatus->{$vmid};
464 my $parse_restore_archive = sub {
465 my ($storecfg, $archive) = @_;
467 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
469 if (defined($archive_storeid)) {
470 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
471 if ($scfg->{type
} eq 'pbs') {
478 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
486 __PACKAGE__-
>register_method({
490 description
=> "Create or restore a virtual machine.",
492 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
493 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
494 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
495 user
=> 'all', # check inside
500 additionalProperties
=> 0,
501 properties
=> PVE
::QemuServer
::json_config_properties
(
503 node
=> get_standard_option
('pve-node'),
504 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
506 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
510 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
512 storage
=> get_standard_option
('pve-storage-id', {
513 description
=> "Default storage.",
515 completion
=> \
&PVE
::QemuServer
::complete_storage
,
520 description
=> "Allow to overwrite existing VM.",
521 requires
=> 'archive',
526 description
=> "Assign a unique random ethernet address.",
527 requires
=> 'archive',
532 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
533 requires
=> 'archive',
537 type
=> 'string', format
=> 'pve-poolid',
538 description
=> "Add the VM to the specified pool.",
541 description
=> "Override I/O bandwidth limit (in KiB/s).",
545 default => 'restore limit from datacenter or storage config',
551 description
=> "Start VM after it was created successfully.",
561 my $rpcenv = PVE
::RPCEnvironment
::get
();
562 my $authuser = $rpcenv->get_user();
564 my $node = extract_param
($param, 'node');
565 my $vmid = extract_param
($param, 'vmid');
567 my $archive = extract_param
($param, 'archive');
568 my $is_restore = !!$archive;
570 my $bwlimit = extract_param
($param, 'bwlimit');
571 my $force = extract_param
($param, 'force');
572 my $pool = extract_param
($param, 'pool');
573 my $start_after_create = extract_param
($param, 'start');
574 my $storage = extract_param
($param, 'storage');
575 my $unique = extract_param
($param, 'unique');
576 my $live_restore = extract_param
($param, 'live-restore');
578 if (defined(my $ssh_keys = $param->{sshkeys
})) {
579 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
580 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
583 PVE
::Cluster
::check_cfs_quorum
();
585 my $filename = PVE
::QemuConfig-
>config_file($vmid);
586 my $storecfg = PVE
::Storage
::config
();
588 if (defined($pool)) {
589 $rpcenv->check_pool_exist($pool);
592 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
593 if defined($storage);
595 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
597 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
599 } elsif ($archive && $force && (-f
$filename) &&
600 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
601 # OK: user has VM.Backup permissions, and want to restore an existing VM
607 &$resolve_cdrom_alias($param);
609 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
611 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
613 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
614 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
616 &$check_cpu_model_access($rpcenv, $authuser, $param);
618 foreach my $opt (keys %$param) {
619 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
620 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
621 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
623 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
624 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
628 PVE
::QemuServer
::add_random_macs
($param);
630 my $keystr = join(' ', keys %$param);
631 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
633 if ($archive eq '-') {
634 die "pipe requires cli environment\n"
635 if $rpcenv->{type
} ne 'cli';
636 $archive = { type
=> 'pipe' };
638 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
640 $archive = $parse_restore_archive->($storecfg, $archive);
644 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
646 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
647 die "$emsg $@" if $@;
649 my $restored_data = 0;
650 my $restorefn = sub {
651 my $conf = PVE
::QemuConfig-
>load_config($vmid);
653 PVE
::QemuConfig-
>check_protection($conf, $emsg);
655 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
658 my $restore_options = {
663 live
=> $live_restore,
665 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
666 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
668 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
669 } elsif ($archive->{type
} eq 'pbs') {
670 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
672 die "unknown backup archive type\n";
676 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
677 # Convert restored VM to template if backup was VM template
678 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
679 warn "Convert to template.\n";
680 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
685 # ensure no old replication state are exists
686 PVE
::ReplicationState
::delete_guest_states
($vmid);
688 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
690 if ($start_after_create && !$live_restore) {
691 print "Execute autostart\n";
692 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
698 # ensure no old replication state are exists
699 PVE
::ReplicationState
::delete_guest_states
($vmid);
703 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
707 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
709 if (!$conf->{boot
}) {
710 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
711 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
714 # auto generate uuid if user did not specify smbios1 option
715 if (!$conf->{smbios1
}) {
716 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
719 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
720 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
723 my $machine = $conf->{machine
};
724 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
725 # always pin Windows' machine version on create, they get to easily confused
726 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
727 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
731 PVE
::QemuConfig-
>write_config($vmid, $conf);
737 foreach my $volid (@$vollist) {
738 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
744 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
747 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
749 if ($start_after_create) {
750 print "Execute autostart\n";
751 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
756 my ($code, $worker_name);
758 $worker_name = 'qmrestore';
760 eval { $restorefn->() };
762 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
764 if ($restored_data) {
765 warn "error after data was restored, VM disks should be OK but config may "
766 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
768 warn "error before or during data restore, some or all disks were not "
769 ."completely restored. VM $vmid state is NOT cleaned up.\n";
775 $worker_name = 'qmcreate';
777 eval { $createfn->() };
780 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
781 unlink($conffile) or die "failed to remove config file: $!\n";
789 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
792 __PACKAGE__-
>register_method({
797 description
=> "Directory index",
802 additionalProperties
=> 0,
804 node
=> get_standard_option
('pve-node'),
805 vmid
=> get_standard_option
('pve-vmid'),
813 subdir
=> { type
=> 'string' },
816 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
822 { subdir
=> 'config' },
823 { subdir
=> 'pending' },
824 { subdir
=> 'status' },
825 { subdir
=> 'unlink' },
826 { subdir
=> 'vncproxy' },
827 { subdir
=> 'termproxy' },
828 { subdir
=> 'migrate' },
829 { subdir
=> 'resize' },
830 { subdir
=> 'move' },
832 { subdir
=> 'rrddata' },
833 { subdir
=> 'monitor' },
834 { subdir
=> 'agent' },
835 { subdir
=> 'snapshot' },
836 { subdir
=> 'spiceproxy' },
837 { subdir
=> 'sendkey' },
838 { subdir
=> 'firewall' },
844 __PACKAGE__-
>register_method ({
845 subclass
=> "PVE::API2::Firewall::VM",
846 path
=> '{vmid}/firewall',
849 __PACKAGE__-
>register_method ({
850 subclass
=> "PVE::API2::Qemu::Agent",
851 path
=> '{vmid}/agent',
854 __PACKAGE__-
>register_method({
856 path
=> '{vmid}/rrd',
858 protected
=> 1, # fixme: can we avoid that?
860 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
862 description
=> "Read VM RRD statistics (returns PNG)",
864 additionalProperties
=> 0,
866 node
=> get_standard_option
('pve-node'),
867 vmid
=> get_standard_option
('pve-vmid'),
869 description
=> "Specify the time frame you are interested in.",
871 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
874 description
=> "The list of datasources you want to display.",
875 type
=> 'string', format
=> 'pve-configid-list',
878 description
=> "The RRD consolidation function",
880 enum
=> [ 'AVERAGE', 'MAX' ],
888 filename
=> { type
=> 'string' },
894 return PVE
::RRD
::create_rrd_graph
(
895 "pve2-vm/$param->{vmid}", $param->{timeframe
},
896 $param->{ds
}, $param->{cf
});
900 __PACKAGE__-
>register_method({
902 path
=> '{vmid}/rrddata',
904 protected
=> 1, # fixme: can we avoid that?
906 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
908 description
=> "Read VM RRD statistics",
910 additionalProperties
=> 0,
912 node
=> get_standard_option
('pve-node'),
913 vmid
=> get_standard_option
('pve-vmid'),
915 description
=> "Specify the time frame you are interested in.",
917 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
920 description
=> "The RRD consolidation function",
922 enum
=> [ 'AVERAGE', 'MAX' ],
937 return PVE
::RRD
::create_rrd_data
(
938 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
942 __PACKAGE__-
>register_method({
944 path
=> '{vmid}/config',
947 description
=> "Get the virtual machine configuration with pending configuration " .
948 "changes applied. Set the 'current' parameter to get the current configuration instead.",
950 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
953 additionalProperties
=> 0,
955 node
=> get_standard_option
('pve-node'),
956 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
958 description
=> "Get current values (instead of pending values).",
963 snapshot
=> get_standard_option
('pve-snapshot-name', {
964 description
=> "Fetch config values from given snapshot.",
967 my ($cmd, $pname, $cur, $args) = @_;
968 PVE
::QemuConfig-
>snapshot_list($args->[0]);
974 description
=> "The VM configuration.",
976 properties
=> PVE
::QemuServer
::json_config_properties
({
979 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
986 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
987 current
=> "cannot use 'snapshot' parameter with 'current'"})
988 if ($param->{snapshot
} && $param->{current
});
991 if ($param->{snapshot
}) {
992 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
994 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
996 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1001 __PACKAGE__-
>register_method({
1002 name
=> 'vm_pending',
1003 path
=> '{vmid}/pending',
1006 description
=> "Get the virtual machine configuration with both current and pending values.",
1008 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1011 additionalProperties
=> 0,
1013 node
=> get_standard_option
('pve-node'),
1014 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1023 description
=> "Configuration option name.",
1027 description
=> "Current value.",
1032 description
=> "Pending value.",
1037 description
=> "Indicates a pending delete request if present and not 0. " .
1038 "The value 2 indicates a force-delete request.",
1050 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1052 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1054 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1055 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1057 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1060 # POST/PUT {vmid}/config implementation
1062 # The original API used PUT (idempotent) an we assumed that all operations
1063 # are fast. But it turned out that almost any configuration change can
1064 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1065 # time to complete and have side effects (not idempotent).
1067 # The new implementation uses POST and forks a worker process. We added
1068 # a new option 'background_delay'. If specified we wait up to
1069 # 'background_delay' second for the worker task to complete. It returns null
1070 # if the task is finished within that time, else we return the UPID.
1072 my $update_vm_api = sub {
1073 my ($param, $sync) = @_;
1075 my $rpcenv = PVE
::RPCEnvironment
::get
();
1077 my $authuser = $rpcenv->get_user();
1079 my $node = extract_param
($param, 'node');
1081 my $vmid = extract_param
($param, 'vmid');
1083 my $digest = extract_param
($param, 'digest');
1085 my $background_delay = extract_param
($param, 'background_delay');
1087 if (defined(my $cipassword = $param->{cipassword
})) {
1088 # Same logic as in cloud-init (but with the regex fixed...)
1089 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1090 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1093 my @paramarr = (); # used for log message
1094 foreach my $key (sort keys %$param) {
1095 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1096 push @paramarr, "-$key", $value;
1099 my $skiplock = extract_param
($param, 'skiplock');
1100 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1101 if $skiplock && $authuser ne 'root@pam';
1103 my $delete_str = extract_param
($param, 'delete');
1105 my $revert_str = extract_param
($param, 'revert');
1107 my $force = extract_param
($param, 'force');
1109 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1110 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1111 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1114 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1116 my $storecfg = PVE
::Storage
::config
();
1118 my $defaults = PVE
::QemuServer
::load_defaults
();
1120 &$resolve_cdrom_alias($param);
1122 # now try to verify all parameters
1125 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1126 if (!PVE
::QemuServer
::option_exists
($opt)) {
1127 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1130 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1131 "-revert $opt' at the same time" })
1132 if defined($param->{$opt});
1134 $revert->{$opt} = 1;
1138 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1139 $opt = 'ide2' if $opt eq 'cdrom';
1141 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1142 "-delete $opt' at the same time" })
1143 if defined($param->{$opt});
1145 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1146 "-revert $opt' at the same time" })
1149 if (!PVE
::QemuServer
::option_exists
($opt)) {
1150 raise_param_exc
({ delete => "unknown option '$opt'" });
1156 my $repl_conf = PVE
::ReplicationConfig-
>new();
1157 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1158 my $check_replication = sub {
1160 return if !$is_replicated;
1161 my $volid = $drive->{file
};
1162 return if !$volid || !($drive->{replicate
}//1);
1163 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1165 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1166 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1167 if !defined($storeid);
1169 return if defined($volname) && $volname eq 'cloudinit';
1172 if ($volid =~ $NEW_DISK_RE) {
1174 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1176 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1178 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1179 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1180 return if $scfg->{shared
};
1181 die "cannot add non-replicatable volume to a replicated VM\n";
1184 foreach my $opt (keys %$param) {
1185 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1186 # cleanup drive path
1187 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1188 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1189 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1190 $check_replication->($drive);
1191 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1192 } elsif ($opt =~ m/^net(\d+)$/) {
1194 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1195 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1196 } elsif ($opt eq 'vmgenid') {
1197 if ($param->{$opt} eq '1') {
1198 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1200 } elsif ($opt eq 'hookscript') {
1201 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1202 raise_param_exc
({ $opt => $@ }) if $@;
1206 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1208 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1210 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1212 my $updatefn = sub {
1214 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1216 die "checksum missmatch (file change by other user?)\n"
1217 if $digest && $digest ne $conf->{digest
};
1219 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1221 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1222 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1223 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1224 delete $conf->{lock}; # for check lock check, not written out
1225 push @delete, 'lock'; # this is the real deal to write it out
1227 push @delete, 'runningmachine' if $conf->{runningmachine
};
1228 push @delete, 'runningcpu' if $conf->{runningcpu
};
1231 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1233 foreach my $opt (keys %$revert) {
1234 if (defined($conf->{$opt})) {
1235 $param->{$opt} = $conf->{$opt};
1236 } elsif (defined($conf->{pending
}->{$opt})) {
1241 if ($param->{memory
} || defined($param->{balloon
})) {
1242 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1243 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1245 die "balloon value too large (must be smaller than assigned memory)\n"
1246 if $balloon && $balloon > $maxmem;
1249 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1253 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1255 # write updates to pending section
1257 my $modified = {}; # record what $option we modify
1260 if (my $boot = $conf->{boot
}) {
1261 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1262 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1264 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1266 my $check_drive_perms = sub {
1267 my ($opt, $val) = @_;
1268 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1269 # FIXME: cloudinit: CDROM or Disk?
1270 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1271 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1273 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1277 foreach my $opt (@delete) {
1278 $modified->{$opt} = 1;
1279 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1281 # value of what we want to delete, independent if pending or not
1282 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1283 if (!defined($val)) {
1284 warn "cannot delete '$opt' - not set in current configuration!\n";
1285 $modified->{$opt} = 0;
1288 my $is_pending_val = defined($conf->{pending
}->{$opt});
1289 delete $conf->{pending
}->{$opt};
1291 # remove from bootorder if necessary
1292 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1293 @bootorder = grep {$_ ne $opt} @bootorder;
1294 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1295 $modified->{boot
} = 1;
1298 if ($opt =~ m/^unused/) {
1299 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1300 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1301 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1302 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1303 delete $conf->{$opt};
1304 PVE
::QemuConfig-
>write_config($vmid, $conf);
1306 } elsif ($opt eq 'vmstate') {
1307 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1308 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1309 delete $conf->{$opt};
1310 PVE
::QemuConfig-
>write_config($vmid, $conf);
1312 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1313 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1314 $check_drive_perms->($opt, $val);
1315 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1317 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1318 PVE
::QemuConfig-
>write_config($vmid, $conf);
1319 } elsif ($opt =~ m/^serial\d+$/) {
1320 if ($val eq 'socket') {
1321 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1322 } elsif ($authuser ne 'root@pam') {
1323 die "only root can delete '$opt' config for real devices\n";
1325 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1326 PVE
::QemuConfig-
>write_config($vmid, $conf);
1327 } elsif ($opt =~ m/^usb\d+$/) {
1328 if ($val =~ m/spice/) {
1329 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1330 } elsif ($authuser ne 'root@pam') {
1331 die "only root can delete '$opt' config for real devices\n";
1333 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1334 PVE
::QemuConfig-
>write_config($vmid, $conf);
1336 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1337 PVE
::QemuConfig-
>write_config($vmid, $conf);
1341 foreach my $opt (keys %$param) { # add/change
1342 $modified->{$opt} = 1;
1343 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1344 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1346 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1348 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1350 if ($conf->{$opt}) {
1351 $check_drive_perms->($opt, $conf->{$opt});
1355 $check_drive_perms->($opt, $param->{$opt});
1356 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1357 if defined($conf->{pending
}->{$opt});
1359 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1361 # append new CD drives to bootorder to mark them bootable
1362 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1363 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1364 push @bootorder, $opt;
1365 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1366 $modified->{boot
} = 1;
1368 } elsif ($opt =~ m/^serial\d+/) {
1369 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1370 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1371 } elsif ($authuser ne 'root@pam') {
1372 die "only root can modify '$opt' config for real devices\n";
1374 $conf->{pending
}->{$opt} = $param->{$opt};
1375 } elsif ($opt =~ m/^usb\d+/) {
1376 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1377 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1378 } elsif ($authuser ne 'root@pam') {
1379 die "only root can modify '$opt' config for real devices\n";
1381 $conf->{pending
}->{$opt} = $param->{$opt};
1383 $conf->{pending
}->{$opt} = $param->{$opt};
1385 if ($opt eq 'boot') {
1386 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1387 if ($new_bootcfg->{order
}) {
1388 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1389 for my $dev (@devs) {
1390 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1391 my $deleted = grep {$_ eq $dev} @delete;
1392 die "invalid bootorder: device '$dev' does not exist'\n"
1393 if !$exists || $deleted;
1396 # remove legacy boot order settings if new one set
1397 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1398 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1399 if $conf->{bootdisk
};
1403 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1404 PVE
::QemuConfig-
>write_config($vmid, $conf);
1407 # remove pending changes when nothing changed
1408 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1409 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1410 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1412 return if !scalar(keys %{$conf->{pending
}});
1414 my $running = PVE
::QemuServer
::check_running
($vmid);
1416 # apply pending changes
1418 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1422 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1424 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1426 raise_param_exc
($errors) if scalar(keys %$errors);
1435 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1437 if ($background_delay) {
1439 # Note: It would be better to do that in the Event based HTTPServer
1440 # to avoid blocking call to sleep.
1442 my $end_time = time() + $background_delay;
1444 my $task = PVE
::Tools
::upid_decode
($upid);
1447 while (time() < $end_time) {
1448 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1450 sleep(1); # this gets interrupted when child process ends
1454 my $status = PVE
::Tools
::upid_read_status
($upid);
1455 return if !PVE
::Tools
::upid_status_is_error
($status);
1464 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1467 my $vm_config_perm_list = [
1472 'VM.Config.Network',
1474 'VM.Config.Options',
1475 'VM.Config.Cloudinit',
1478 __PACKAGE__-
>register_method({
1479 name
=> 'update_vm_async',
1480 path
=> '{vmid}/config',
1484 description
=> "Set virtual machine options (asynchrounous API).",
1486 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1489 additionalProperties
=> 0,
1490 properties
=> PVE
::QemuServer
::json_config_properties
(
1492 node
=> get_standard_option
('pve-node'),
1493 vmid
=> get_standard_option
('pve-vmid'),
1494 skiplock
=> get_standard_option
('skiplock'),
1496 type
=> 'string', format
=> 'pve-configid-list',
1497 description
=> "A list of settings you want to delete.",
1501 type
=> 'string', format
=> 'pve-configid-list',
1502 description
=> "Revert a pending change.",
1507 description
=> $opt_force_description,
1509 requires
=> 'delete',
1513 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1517 background_delay
=> {
1519 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1530 code
=> $update_vm_api,
1533 __PACKAGE__-
>register_method({
1534 name
=> 'update_vm',
1535 path
=> '{vmid}/config',
1539 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1541 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1544 additionalProperties
=> 0,
1545 properties
=> PVE
::QemuServer
::json_config_properties
(
1547 node
=> get_standard_option
('pve-node'),
1548 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1549 skiplock
=> get_standard_option
('skiplock'),
1551 type
=> 'string', format
=> 'pve-configid-list',
1552 description
=> "A list of settings you want to delete.",
1556 type
=> 'string', format
=> 'pve-configid-list',
1557 description
=> "Revert a pending change.",
1562 description
=> $opt_force_description,
1564 requires
=> 'delete',
1568 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1574 returns
=> { type
=> 'null' },
1577 &$update_vm_api($param, 1);
1582 __PACKAGE__-
>register_method({
1583 name
=> 'destroy_vm',
1588 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1589 ." and firewall rules",
1591 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1594 additionalProperties
=> 0,
1596 node
=> get_standard_option
('pve-node'),
1597 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1598 skiplock
=> get_standard_option
('skiplock'),
1601 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1604 'destroy-unreferenced-disks' => {
1606 description
=> "If set, destroy additionally all disks not referenced in the config"
1607 ." but with a matching VMID from all enabled storages.",
1619 my $rpcenv = PVE
::RPCEnvironment
::get
();
1620 my $authuser = $rpcenv->get_user();
1621 my $vmid = $param->{vmid
};
1623 my $skiplock = $param->{skiplock
};
1624 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1625 if $skiplock && $authuser ne 'root@pam';
1627 my $early_checks = sub {
1629 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1630 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1632 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1634 if (!$param->{purge
}) {
1635 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1637 # don't allow destroy if with replication jobs but no purge param
1638 my $repl_conf = PVE
::ReplicationConfig-
>new();
1639 $repl_conf->check_for_existing_jobs($vmid);
1642 die "VM $vmid is running - destroy failed\n"
1643 if PVE
::QemuServer
::check_running
($vmid);
1653 my $storecfg = PVE
::Storage
::config
();
1655 syslog
('info', "destroy VM $vmid: $upid\n");
1656 PVE
::QemuConfig-
>lock_config($vmid, sub {
1657 # repeat, config might have changed
1658 my $ha_managed = $early_checks->();
1660 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1662 PVE
::QemuServer
::destroy_vm
(
1665 $skiplock, { lock => 'destroyed' },
1666 $purge_unreferenced,
1669 PVE
::AccessControl
::remove_vm_access
($vmid);
1670 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1671 if ($param->{purge
}) {
1672 print "purging VM $vmid from related configurations..\n";
1673 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1674 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1677 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1678 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1682 # only now remove the zombie config, else we can have reuse race
1683 PVE
::QemuConfig-
>destroy_config($vmid);
1687 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1690 __PACKAGE__-
>register_method({
1692 path
=> '{vmid}/unlink',
1696 description
=> "Unlink/delete disk images.",
1698 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1701 additionalProperties
=> 0,
1703 node
=> get_standard_option
('pve-node'),
1704 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1706 type
=> 'string', format
=> 'pve-configid-list',
1707 description
=> "A list of disk IDs you want to delete.",
1711 description
=> $opt_force_description,
1716 returns
=> { type
=> 'null'},
1720 $param->{delete} = extract_param
($param, 'idlist');
1722 __PACKAGE__-
>update_vm($param);
1727 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1728 my $gen_rand_chars = sub {
1731 die "invalid length $length" if $length < 1;
1733 my $min = ord('!'); # first printable ascii
1735 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1736 die "failed to generate random bytes!\n"
1739 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1746 __PACKAGE__-
>register_method({
1748 path
=> '{vmid}/vncproxy',
1752 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1754 description
=> "Creates a TCP VNC proxy connections.",
1756 additionalProperties
=> 0,
1758 node
=> get_standard_option
('pve-node'),
1759 vmid
=> get_standard_option
('pve-vmid'),
1763 description
=> "starts websockify instead of vncproxy",
1765 'generate-password' => {
1769 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1774 additionalProperties
=> 0,
1776 user
=> { type
=> 'string' },
1777 ticket
=> { type
=> 'string' },
1780 description
=> "Returned if requested with 'generate-password' param."
1781 ." Consists of printable ASCII characters ('!' .. '~').",
1784 cert
=> { type
=> 'string' },
1785 port
=> { type
=> 'integer' },
1786 upid
=> { type
=> 'string' },
1792 my $rpcenv = PVE
::RPCEnvironment
::get
();
1794 my $authuser = $rpcenv->get_user();
1796 my $vmid = $param->{vmid
};
1797 my $node = $param->{node
};
1798 my $websocket = $param->{websocket
};
1800 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1804 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1805 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1808 my $authpath = "/vms/$vmid";
1810 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1811 my $password = $ticket;
1812 if ($param->{'generate-password'}) {
1813 $password = $gen_rand_chars->(8);
1816 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1822 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1823 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1824 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1825 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1826 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1828 $family = PVE
::Tools
::get_host_address_family
($node);
1831 my $port = PVE
::Tools
::next_vnc_port
($family);
1838 syslog
('info', "starting vnc proxy $upid\n");
1842 if (defined($serial)) {
1844 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1846 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1847 '-timeout', $timeout, '-authpath', $authpath,
1848 '-perm', 'Sys.Console'];
1850 if ($param->{websocket
}) {
1851 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1852 push @$cmd, '-notls', '-listen', 'localhost';
1855 push @$cmd, '-c', @$remcmd, @$termcmd;
1857 PVE
::Tools
::run_command
($cmd);
1861 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1863 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1865 my $sock = IO
::Socket
::IP-
>new(
1870 GetAddrInfoFlags
=> 0,
1871 ) or die "failed to create socket: $!\n";
1872 # Inside the worker we shouldn't have any previous alarms
1873 # running anyway...:
1875 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1877 accept(my $cli, $sock) or die "connection failed: $!\n";
1880 if (PVE
::Tools
::run_command
($cmd,
1881 output
=> '>&'.fileno($cli),
1882 input
=> '<&'.fileno($cli),
1885 die "Failed to run vncproxy.\n";
1892 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1894 PVE
::Tools
::wait_for_vnc_port
($port);
1903 $res->{password
} = $password if $param->{'generate-password'};
1908 __PACKAGE__-
>register_method({
1909 name
=> 'termproxy',
1910 path
=> '{vmid}/termproxy',
1914 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1916 description
=> "Creates a TCP proxy connections.",
1918 additionalProperties
=> 0,
1920 node
=> get_standard_option
('pve-node'),
1921 vmid
=> get_standard_option
('pve-vmid'),
1925 enum
=> [qw(serial0 serial1 serial2 serial3)],
1926 description
=> "opens a serial terminal (defaults to display)",
1931 additionalProperties
=> 0,
1933 user
=> { type
=> 'string' },
1934 ticket
=> { type
=> 'string' },
1935 port
=> { type
=> 'integer' },
1936 upid
=> { type
=> 'string' },
1942 my $rpcenv = PVE
::RPCEnvironment
::get
();
1944 my $authuser = $rpcenv->get_user();
1946 my $vmid = $param->{vmid
};
1947 my $node = $param->{node
};
1948 my $serial = $param->{serial
};
1950 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1952 if (!defined($serial)) {
1954 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1955 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1959 my $authpath = "/vms/$vmid";
1961 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1966 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1967 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1968 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1969 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1970 push @$remcmd, '--';
1972 $family = PVE
::Tools
::get_host_address_family
($node);
1975 my $port = PVE
::Tools
::next_vnc_port
($family);
1977 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1978 push @$termcmd, '-iface', $serial if $serial;
1983 syslog
('info', "starting qemu termproxy $upid\n");
1985 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1986 '--perm', 'VM.Console', '--'];
1987 push @$cmd, @$remcmd, @$termcmd;
1989 PVE
::Tools
::run_command
($cmd);
1992 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1994 PVE
::Tools
::wait_for_vnc_port
($port);
2004 __PACKAGE__-
>register_method({
2005 name
=> 'vncwebsocket',
2006 path
=> '{vmid}/vncwebsocket',
2009 description
=> "You also need to pass a valid ticket (vncticket).",
2010 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2012 description
=> "Opens a weksocket for VNC traffic.",
2014 additionalProperties
=> 0,
2016 node
=> get_standard_option
('pve-node'),
2017 vmid
=> get_standard_option
('pve-vmid'),
2019 description
=> "Ticket from previous call to vncproxy.",
2024 description
=> "Port number returned by previous vncproxy call.",
2034 port
=> { type
=> 'string' },
2040 my $rpcenv = PVE
::RPCEnvironment
::get
();
2042 my $authuser = $rpcenv->get_user();
2044 my $vmid = $param->{vmid
};
2045 my $node = $param->{node
};
2047 my $authpath = "/vms/$vmid";
2049 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2051 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2053 # Note: VNC ports are acessible from outside, so we do not gain any
2054 # security if we verify that $param->{port} belongs to VM $vmid. This
2055 # check is done by verifying the VNC ticket (inside VNC protocol).
2057 my $port = $param->{port
};
2059 return { port
=> $port };
2062 __PACKAGE__-
>register_method({
2063 name
=> 'spiceproxy',
2064 path
=> '{vmid}/spiceproxy',
2069 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2071 description
=> "Returns a SPICE configuration to connect to the VM.",
2073 additionalProperties
=> 0,
2075 node
=> get_standard_option
('pve-node'),
2076 vmid
=> get_standard_option
('pve-vmid'),
2077 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2080 returns
=> get_standard_option
('remote-viewer-config'),
2084 my $rpcenv = PVE
::RPCEnvironment
::get
();
2086 my $authuser = $rpcenv->get_user();
2088 my $vmid = $param->{vmid
};
2089 my $node = $param->{node
};
2090 my $proxy = $param->{proxy
};
2092 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2093 my $title = "VM $vmid";
2094 $title .= " - ". $conf->{name
} if $conf->{name
};
2096 my $port = PVE
::QemuServer
::spice_port
($vmid);
2098 my ($ticket, undef, $remote_viewer_config) =
2099 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2101 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2102 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2104 return $remote_viewer_config;
2107 __PACKAGE__-
>register_method({
2109 path
=> '{vmid}/status',
2112 description
=> "Directory index",
2117 additionalProperties
=> 0,
2119 node
=> get_standard_option
('pve-node'),
2120 vmid
=> get_standard_option
('pve-vmid'),
2128 subdir
=> { type
=> 'string' },
2131 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2137 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2140 { subdir
=> 'current' },
2141 { subdir
=> 'start' },
2142 { subdir
=> 'stop' },
2143 { subdir
=> 'reset' },
2144 { subdir
=> 'shutdown' },
2145 { subdir
=> 'suspend' },
2146 { subdir
=> 'reboot' },
2152 __PACKAGE__-
>register_method({
2153 name
=> 'vm_status',
2154 path
=> '{vmid}/status/current',
2157 protected
=> 1, # qemu pid files are only readable by root
2158 description
=> "Get virtual machine status.",
2160 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2163 additionalProperties
=> 0,
2165 node
=> get_standard_option
('pve-node'),
2166 vmid
=> get_standard_option
('pve-vmid'),
2172 %$PVE::QemuServer
::vmstatus_return_properties
,
2174 description
=> "HA manager service status.",
2178 description
=> "Qemu VGA configuration supports spice.",
2183 description
=> "Qemu GuestAgent enabled in config.",
2193 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2195 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2196 my $status = $vmstatus->{$param->{vmid
}};
2198 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2200 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2201 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2206 __PACKAGE__-
>register_method({
2208 path
=> '{vmid}/status/start',
2212 description
=> "Start virtual machine.",
2214 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2217 additionalProperties
=> 0,
2219 node
=> get_standard_option
('pve-node'),
2220 vmid
=> get_standard_option
('pve-vmid',
2221 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2222 skiplock
=> get_standard_option
('skiplock'),
2223 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2224 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2227 enum
=> ['secure', 'insecure'],
2228 description
=> "Migration traffic is encrypted using an SSH " .
2229 "tunnel by default. On secure, completely private networks " .
2230 "this can be disabled to increase performance.",
2233 migration_network
=> {
2234 type
=> 'string', format
=> 'CIDR',
2235 description
=> "CIDR of the (sub) network that is used for migration.",
2238 machine
=> get_standard_option
('pve-qemu-machine'),
2240 description
=> "Override QEMU's -cpu argument with the given string.",
2244 targetstorage
=> get_standard_option
('pve-targetstorage'),
2246 description
=> "Wait maximal timeout seconds.",
2249 default => 'max(30, vm memory in GiB)',
2260 my $rpcenv = PVE
::RPCEnvironment
::get
();
2261 my $authuser = $rpcenv->get_user();
2263 my $node = extract_param
($param, 'node');
2264 my $vmid = extract_param
($param, 'vmid');
2265 my $timeout = extract_param
($param, 'timeout');
2267 my $machine = extract_param
($param, 'machine');
2268 my $force_cpu = extract_param
($param, 'force-cpu');
2270 my $get_root_param = sub {
2271 my $value = extract_param
($param, $_[0]);
2272 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2273 if $value && $authuser ne 'root@pam';
2277 my $stateuri = $get_root_param->('stateuri');
2278 my $skiplock = $get_root_param->('skiplock');
2279 my $migratedfrom = $get_root_param->('migratedfrom');
2280 my $migration_type = $get_root_param->('migration_type');
2281 my $migration_network = $get_root_param->('migration_network');
2282 my $targetstorage = $get_root_param->('targetstorage');
2286 if ($targetstorage) {
2287 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2289 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2290 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2294 # read spice ticket from STDIN
2296 my $nbd_protocol_version = 0;
2297 my $replicated_volumes = {};
2298 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2299 while (defined(my $line = <STDIN
>)) {
2301 if ($line =~ m/^spice_ticket: (.+)$/) {
2303 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2304 $nbd_protocol_version = $1;
2305 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2306 $replicated_volumes->{$1} = 1;
2308 # fallback for old source node
2309 $spice_ticket = $line;
2314 PVE
::Cluster
::check_cfs_quorum
();
2316 my $storecfg = PVE
::Storage
::config
();
2318 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2322 print "Requesting HA start for VM $vmid\n";
2324 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2325 PVE
::Tools
::run_command
($cmd);
2329 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2336 syslog
('info', "start VM $vmid: $upid\n");
2338 my $migrate_opts = {
2339 migratedfrom
=> $migratedfrom,
2340 spice_ticket
=> $spice_ticket,
2341 network
=> $migration_network,
2342 type
=> $migration_type,
2343 storagemap
=> $storagemap,
2344 nbd_proto_version
=> $nbd_protocol_version,
2345 replicated_volumes
=> $replicated_volumes,
2349 statefile
=> $stateuri,
2350 skiplock
=> $skiplock,
2351 forcemachine
=> $machine,
2352 timeout
=> $timeout,
2353 forcecpu
=> $force_cpu,
2356 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2360 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2364 __PACKAGE__-
>register_method({
2366 path
=> '{vmid}/status/stop',
2370 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2371 "is akin to pulling the power plug of a running computer and may damage the VM data",
2373 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2376 additionalProperties
=> 0,
2378 node
=> get_standard_option
('pve-node'),
2379 vmid
=> get_standard_option
('pve-vmid',
2380 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2381 skiplock
=> get_standard_option
('skiplock'),
2382 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2384 description
=> "Wait maximal timeout seconds.",
2390 description
=> "Do not deactivate storage volumes.",
2403 my $rpcenv = PVE
::RPCEnvironment
::get
();
2404 my $authuser = $rpcenv->get_user();
2406 my $node = extract_param
($param, 'node');
2407 my $vmid = extract_param
($param, 'vmid');
2409 my $skiplock = extract_param
($param, 'skiplock');
2410 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2411 if $skiplock && $authuser ne 'root@pam';
2413 my $keepActive = extract_param
($param, 'keepActive');
2414 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2415 if $keepActive && $authuser ne 'root@pam';
2417 my $migratedfrom = extract_param
($param, 'migratedfrom');
2418 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2419 if $migratedfrom && $authuser ne 'root@pam';
2422 my $storecfg = PVE
::Storage
::config
();
2424 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2429 print "Requesting HA stop for VM $vmid\n";
2431 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2432 PVE
::Tools
::run_command
($cmd);
2436 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2442 syslog
('info', "stop VM $vmid: $upid\n");
2444 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2445 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2449 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2453 __PACKAGE__-
>register_method({
2455 path
=> '{vmid}/status/reset',
2459 description
=> "Reset virtual machine.",
2461 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2464 additionalProperties
=> 0,
2466 node
=> get_standard_option
('pve-node'),
2467 vmid
=> get_standard_option
('pve-vmid',
2468 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2469 skiplock
=> get_standard_option
('skiplock'),
2478 my $rpcenv = PVE
::RPCEnvironment
::get
();
2480 my $authuser = $rpcenv->get_user();
2482 my $node = extract_param
($param, 'node');
2484 my $vmid = extract_param
($param, 'vmid');
2486 my $skiplock = extract_param
($param, 'skiplock');
2487 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2488 if $skiplock && $authuser ne 'root@pam';
2490 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2495 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2500 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2503 __PACKAGE__-
>register_method({
2504 name
=> 'vm_shutdown',
2505 path
=> '{vmid}/status/shutdown',
2509 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2510 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2512 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2515 additionalProperties
=> 0,
2517 node
=> get_standard_option
('pve-node'),
2518 vmid
=> get_standard_option
('pve-vmid',
2519 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2520 skiplock
=> get_standard_option
('skiplock'),
2522 description
=> "Wait maximal timeout seconds.",
2528 description
=> "Make sure the VM stops.",
2534 description
=> "Do not deactivate storage volumes.",
2547 my $rpcenv = PVE
::RPCEnvironment
::get
();
2548 my $authuser = $rpcenv->get_user();
2550 my $node = extract_param
($param, 'node');
2551 my $vmid = extract_param
($param, 'vmid');
2553 my $skiplock = extract_param
($param, 'skiplock');
2554 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2555 if $skiplock && $authuser ne 'root@pam';
2557 my $keepActive = extract_param
($param, 'keepActive');
2558 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2559 if $keepActive && $authuser ne 'root@pam';
2561 my $storecfg = PVE
::Storage
::config
();
2565 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2566 # otherwise, we will infer a shutdown command, but run into the timeout,
2567 # then when the vm is resumed, it will instantly shutdown
2569 # checking the qmp status here to get feedback to the gui/cli/api
2570 # and the status query should not take too long
2571 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2572 if ($param->{forceStop
}) {
2573 warn "VM is paused - stop instead of shutdown\n";
2576 die "VM is paused - cannot shutdown\n";
2580 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2582 my $timeout = $param->{timeout
} // 60;
2586 print "Requesting HA stop for VM $vmid\n";
2588 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2589 PVE
::Tools
::run_command
($cmd);
2593 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2600 syslog
('info', "shutdown VM $vmid: $upid\n");
2602 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2603 $shutdown, $param->{forceStop
}, $keepActive);
2607 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2611 __PACKAGE__-
>register_method({
2612 name
=> 'vm_reboot',
2613 path
=> '{vmid}/status/reboot',
2617 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2619 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2622 additionalProperties
=> 0,
2624 node
=> get_standard_option
('pve-node'),
2625 vmid
=> get_standard_option
('pve-vmid',
2626 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2628 description
=> "Wait maximal timeout seconds for the shutdown.",
2641 my $rpcenv = PVE
::RPCEnvironment
::get
();
2642 my $authuser = $rpcenv->get_user();
2644 my $node = extract_param
($param, 'node');
2645 my $vmid = extract_param
($param, 'vmid');
2647 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2649 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2654 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2655 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2659 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2662 __PACKAGE__-
>register_method({
2663 name
=> 'vm_suspend',
2664 path
=> '{vmid}/status/suspend',
2668 description
=> "Suspend virtual machine.",
2670 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2671 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2672 " on the storage for the vmstate.",
2673 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2676 additionalProperties
=> 0,
2678 node
=> get_standard_option
('pve-node'),
2679 vmid
=> get_standard_option
('pve-vmid',
2680 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2681 skiplock
=> get_standard_option
('skiplock'),
2686 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2688 statestorage
=> get_standard_option
('pve-storage-id', {
2689 description
=> "The storage for the VM state",
2690 requires
=> 'todisk',
2692 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2702 my $rpcenv = PVE
::RPCEnvironment
::get
();
2703 my $authuser = $rpcenv->get_user();
2705 my $node = extract_param
($param, 'node');
2706 my $vmid = extract_param
($param, 'vmid');
2708 my $todisk = extract_param
($param, 'todisk') // 0;
2710 my $statestorage = extract_param
($param, 'statestorage');
2712 my $skiplock = extract_param
($param, 'skiplock');
2713 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2714 if $skiplock && $authuser ne 'root@pam';
2716 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2718 die "Cannot suspend HA managed VM to disk\n"
2719 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2721 # early check for storage permission, for better user feedback
2723 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2725 if (!$statestorage) {
2726 # get statestorage from config if none is given
2727 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2728 my $storecfg = PVE
::Storage
::config
();
2729 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2732 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2738 syslog
('info', "suspend VM $vmid: $upid\n");
2740 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2745 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2746 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2749 __PACKAGE__-
>register_method({
2750 name
=> 'vm_resume',
2751 path
=> '{vmid}/status/resume',
2755 description
=> "Resume virtual machine.",
2757 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2760 additionalProperties
=> 0,
2762 node
=> get_standard_option
('pve-node'),
2763 vmid
=> get_standard_option
('pve-vmid',
2764 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2765 skiplock
=> get_standard_option
('skiplock'),
2766 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2776 my $rpcenv = PVE
::RPCEnvironment
::get
();
2778 my $authuser = $rpcenv->get_user();
2780 my $node = extract_param
($param, 'node');
2782 my $vmid = extract_param
($param, 'vmid');
2784 my $skiplock = extract_param
($param, 'skiplock');
2785 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2786 if $skiplock && $authuser ne 'root@pam';
2788 my $nocheck = extract_param
($param, 'nocheck');
2789 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2790 if $nocheck && $authuser ne 'root@pam';
2792 my $to_disk_suspended;
2794 PVE
::QemuConfig-
>lock_config($vmid, sub {
2795 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2796 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2800 die "VM $vmid not running\n"
2801 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2806 syslog
('info', "resume VM $vmid: $upid\n");
2808 if (!$to_disk_suspended) {
2809 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2811 my $storecfg = PVE
::Storage
::config
();
2812 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2818 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2821 __PACKAGE__-
>register_method({
2822 name
=> 'vm_sendkey',
2823 path
=> '{vmid}/sendkey',
2827 description
=> "Send key event to virtual machine.",
2829 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2832 additionalProperties
=> 0,
2834 node
=> get_standard_option
('pve-node'),
2835 vmid
=> get_standard_option
('pve-vmid',
2836 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2837 skiplock
=> get_standard_option
('skiplock'),
2839 description
=> "The key (qemu monitor encoding).",
2844 returns
=> { type
=> 'null'},
2848 my $rpcenv = PVE
::RPCEnvironment
::get
();
2850 my $authuser = $rpcenv->get_user();
2852 my $node = extract_param
($param, 'node');
2854 my $vmid = extract_param
($param, 'vmid');
2856 my $skiplock = extract_param
($param, 'skiplock');
2857 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2858 if $skiplock && $authuser ne 'root@pam';
2860 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2865 __PACKAGE__-
>register_method({
2866 name
=> 'vm_feature',
2867 path
=> '{vmid}/feature',
2871 description
=> "Check if feature for virtual machine is available.",
2873 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2876 additionalProperties
=> 0,
2878 node
=> get_standard_option
('pve-node'),
2879 vmid
=> get_standard_option
('pve-vmid'),
2881 description
=> "Feature to check.",
2883 enum
=> [ 'snapshot', 'clone', 'copy' ],
2885 snapname
=> get_standard_option
('pve-snapshot-name', {
2893 hasFeature
=> { type
=> 'boolean' },
2896 items
=> { type
=> 'string' },
2903 my $node = extract_param
($param, 'node');
2905 my $vmid = extract_param
($param, 'vmid');
2907 my $snapname = extract_param
($param, 'snapname');
2909 my $feature = extract_param
($param, 'feature');
2911 my $running = PVE
::QemuServer
::check_running
($vmid);
2913 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2916 my $snap = $conf->{snapshots
}->{$snapname};
2917 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2920 my $storecfg = PVE
::Storage
::config
();
2922 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2923 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2926 hasFeature
=> $hasFeature,
2927 nodes
=> [ keys %$nodelist ],
2931 __PACKAGE__-
>register_method({
2933 path
=> '{vmid}/clone',
2937 description
=> "Create a copy of virtual machine/template.",
2939 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2940 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2941 "'Datastore.AllocateSpace' on any used storage.",
2944 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2946 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2947 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2952 additionalProperties
=> 0,
2954 node
=> get_standard_option
('pve-node'),
2955 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2956 newid
=> get_standard_option
('pve-vmid', {
2957 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2958 description
=> 'VMID for the clone.' }),
2961 type
=> 'string', format
=> 'dns-name',
2962 description
=> "Set a name for the new VM.",
2967 description
=> "Description for the new VM.",
2971 type
=> 'string', format
=> 'pve-poolid',
2972 description
=> "Add the new VM to the specified pool.",
2974 snapname
=> get_standard_option
('pve-snapshot-name', {
2977 storage
=> get_standard_option
('pve-storage-id', {
2978 description
=> "Target storage for full clone.",
2982 description
=> "Target format for file storage. Only valid for full clone.",
2985 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2990 description
=> "Create a full copy of all disks. This is always done when " .
2991 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2993 target
=> get_standard_option
('pve-node', {
2994 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2998 description
=> "Override I/O bandwidth limit (in KiB/s).",
3002 default => 'clone limit from datacenter or storage config',
3012 my $rpcenv = PVE
::RPCEnvironment
::get
();
3013 my $authuser = $rpcenv->get_user();
3015 my $node = extract_param
($param, 'node');
3016 my $vmid = extract_param
($param, 'vmid');
3017 my $newid = extract_param
($param, 'newid');
3018 my $pool = extract_param
($param, 'pool');
3019 $rpcenv->check_pool_exist($pool) if defined($pool);
3021 my $snapname = extract_param
($param, 'snapname');
3022 my $storage = extract_param
($param, 'storage');
3023 my $format = extract_param
($param, 'format');
3024 my $target = extract_param
($param, 'target');
3026 my $localnode = PVE
::INotify
::nodename
();
3028 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3032 PVE
::Cluster
::check_node_exists
($target) if $target;
3034 my $storecfg = PVE
::Storage
::config
();
3037 # check if storage is enabled on local node
3038 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3040 # check if storage is available on target node
3041 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3042 # clone only works if target storage is shared
3043 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3044 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3048 PVE
::Cluster
::check_cfs_quorum
();
3050 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3053 # do all tests after lock but before forking worker - if possible
3055 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3056 PVE
::QemuConfig-
>check_lock($conf);
3058 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3059 die "unexpected state change\n" if $verify_running != $running;
3061 die "snapshot '$snapname' does not exist\n"
3062 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3064 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3066 die "parameter 'storage' not allowed for linked clones\n"
3067 if defined($storage) && !$full;
3069 die "parameter 'format' not allowed for linked clones\n"
3070 if defined($format) && !$full;
3072 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3074 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3076 die "can't clone VM to node '$target' (VM uses local storage)\n"
3077 if $target && !$sharedvm;
3079 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3080 die "unable to create VM $newid: config file already exists\n"
3083 my $newconf = { lock => 'clone' };
3088 foreach my $opt (keys %$oldconf) {
3089 my $value = $oldconf->{$opt};
3091 # do not copy snapshot related info
3092 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3093 $opt eq 'vmstate' || $opt eq 'snapstate';
3095 # no need to copy unused images, because VMID(owner) changes anyways
3096 next if $opt =~ m/^unused\d+$/;
3098 # always change MAC! address
3099 if ($opt =~ m/^net(\d+)$/) {
3100 my $net = PVE
::QemuServer
::parse_net
($value);
3101 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3102 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3103 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3104 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3105 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3106 die "unable to parse drive options for '$opt'\n" if !$drive;
3107 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3108 $newconf->{$opt} = $value; # simply copy configuration
3110 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3111 die "Full clone feature is not supported for drive '$opt'\n"
3112 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3113 $fullclone->{$opt} = 1;
3115 # not full means clone instead of copy
3116 die "Linked clone feature is not supported for drive '$opt'\n"
3117 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3119 $drives->{$opt} = $drive;
3120 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3121 push @$vollist, $drive->{file
};
3124 # copy everything else
3125 $newconf->{$opt} = $value;
3129 # auto generate a new uuid
3130 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3131 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3132 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3133 # auto generate a new vmgenid only if the option was set for template
3134 if ($newconf->{vmgenid
}) {
3135 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3138 delete $newconf->{template
};
3140 if ($param->{name
}) {
3141 $newconf->{name
} = $param->{name
};
3143 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3146 if ($param->{description
}) {
3147 $newconf->{description
} = $param->{description
};
3150 # create empty/temp config - this fails if VM already exists on other node
3151 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3152 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3157 my $newvollist = [];
3164 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3166 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3168 my $bwlimit = extract_param
($param, 'bwlimit');
3170 my $total_jobs = scalar(keys %{$drives});
3173 foreach my $opt (sort keys %$drives) {
3174 my $drive = $drives->{$opt};
3175 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3176 my $completion = $skipcomplete ?
'skip' : 'complete';
3178 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3179 my $storage_list = [ $src_sid ];
3180 push @$storage_list, $storage if defined($storage);
3181 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3183 my $newdrive = PVE
::QemuServer
::clone_disk
(
3202 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3204 PVE
::QemuConfig-
>write_config($newid, $newconf);
3208 delete $newconf->{lock};
3210 # do not write pending changes
3211 if (my @changes = keys %{$newconf->{pending
}}) {
3212 my $pending = join(',', @changes);
3213 warn "found pending changes for '$pending', discarding for clone\n";
3214 delete $newconf->{pending
};
3217 PVE
::QemuConfig-
>write_config($newid, $newconf);
3220 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3221 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3222 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3224 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3225 die "Failed to move config to node '$target' - rename failed: $!\n"
3226 if !rename($conffile, $newconffile);
3229 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3232 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3233 sleep 1; # some storage like rbd need to wait before release volume - really?
3235 foreach my $volid (@$newvollist) {
3236 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3240 PVE
::Firewall
::remove_vmfw_conf
($newid);
3242 unlink $conffile; # avoid races -> last thing before die
3244 die "clone failed: $err";
3250 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3252 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3255 # Aquire exclusive lock lock for $newid
3256 my $lock_target_vm = sub {
3257 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3260 # exclusive lock if VM is running - else shared lock is enough;
3262 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3264 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3268 __PACKAGE__-
>register_method({
3269 name
=> 'move_vm_disk',
3270 path
=> '{vmid}/move_disk',
3274 description
=> "Move volume to different storage.",
3276 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3278 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3279 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3283 additionalProperties
=> 0,
3285 node
=> get_standard_option
('pve-node'),
3286 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3289 description
=> "The disk you want to move.",
3290 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3292 storage
=> get_standard_option
('pve-storage-id', {
3293 description
=> "Target storage.",
3294 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3298 description
=> "Target Format.",
3299 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3304 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3310 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3315 description
=> "Override I/O bandwidth limit (in KiB/s).",
3319 default => 'move limit from datacenter or storage config',
3325 description
=> "the task ID.",
3330 my $rpcenv = PVE
::RPCEnvironment
::get
();
3331 my $authuser = $rpcenv->get_user();
3333 my $node = extract_param
($param, 'node');
3334 my $vmid = extract_param
($param, 'vmid');
3335 my $digest = extract_param
($param, 'digest');
3336 my $disk = extract_param
($param, 'disk');
3337 my $storeid = extract_param
($param, 'storage');
3338 my $format = extract_param
($param, 'format');
3340 my $storecfg = PVE
::Storage
::config
();
3342 my $updatefn = sub {
3343 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3344 PVE
::QemuConfig-
>check_lock($conf);
3346 die "VM config checksum missmatch (file change by other user?)\n"
3347 if $digest && $digest ne $conf->{digest
};
3349 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3351 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3353 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3354 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3356 my $old_volid = $drive->{file
};
3358 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3359 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3363 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3364 (!$format || !$oldfmt || $oldfmt eq $format);
3366 # this only checks snapshots because $disk is passed!
3367 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3368 die "you can't move a disk with snapshots and delete the source\n"
3369 if $snapshotted && $param->{delete};
3371 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3373 my $running = PVE
::QemuServer
::check_running
($vmid);
3375 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3378 my $newvollist = [];
3384 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3386 warn "moving disk with snapshots, snapshots will not be moved!\n"
3389 my $bwlimit = extract_param
($param, 'bwlimit');
3390 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3392 my $newdrive = PVE
::QemuServer
::clone_disk
(
3410 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3412 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3414 # convert moved disk to base if part of template
3415 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3416 if PVE
::QemuConfig-
>is_template($conf);
3418 PVE
::QemuConfig-
>write_config($vmid, $conf);
3420 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3421 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3422 eval { mon_cmd
($vmid, "guest-fstrim") };
3426 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3427 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3433 foreach my $volid (@$newvollist) {
3434 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3437 die "storage migration failed: $err";
3440 if ($param->{delete}) {
3442 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3443 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3449 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3452 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3455 my $check_vm_disks_local = sub {
3456 my ($storecfg, $vmconf, $vmid) = @_;
3458 my $local_disks = {};
3460 # add some more information to the disks e.g. cdrom
3461 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3462 my ($volid, $attr) = @_;
3464 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3466 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3467 return if $scfg->{shared
};
3469 # The shared attr here is just a special case where the vdisk
3470 # is marked as shared manually
3471 return if $attr->{shared
};
3472 return if $attr->{cdrom
} and $volid eq "none";
3474 if (exists $local_disks->{$volid}) {
3475 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3477 $local_disks->{$volid} = $attr;
3478 # ensure volid is present in case it's needed
3479 $local_disks->{$volid}->{volid
} = $volid;
3483 return $local_disks;
3486 __PACKAGE__-
>register_method({
3487 name
=> 'migrate_vm_precondition',
3488 path
=> '{vmid}/migrate',
3492 description
=> "Get preconditions for migration.",
3494 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3497 additionalProperties
=> 0,
3499 node
=> get_standard_option
('pve-node'),
3500 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3501 target
=> get_standard_option
('pve-node', {
3502 description
=> "Target node.",
3503 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3511 running
=> { type
=> 'boolean' },
3515 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3517 not_allowed_nodes
=> {
3520 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3524 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3526 local_resources
=> {
3528 description
=> "List local resources e.g. pci, usb"
3535 my $rpcenv = PVE
::RPCEnvironment
::get
();
3537 my $authuser = $rpcenv->get_user();
3539 PVE
::Cluster
::check_cfs_quorum
();
3543 my $vmid = extract_param
($param, 'vmid');
3544 my $target = extract_param
($param, 'target');
3545 my $localnode = PVE
::INotify
::nodename
();
3549 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3550 my $storecfg = PVE
::Storage
::config
();
3553 # try to detect errors early
3554 PVE
::QemuConfig-
>check_lock($vmconf);
3556 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3558 # if vm is not running, return target nodes where local storage is available
3559 # for offline migration
3560 if (!$res->{running
}) {
3561 $res->{allowed_nodes
} = [];
3562 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3563 delete $checked_nodes->{$localnode};
3565 foreach my $node (keys %$checked_nodes) {
3566 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3567 push @{$res->{allowed_nodes
}}, $node;
3571 $res->{not_allowed_nodes
} = $checked_nodes;
3575 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3576 $res->{local_disks
} = [ values %$local_disks ];;
3578 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3580 $res->{local_resources
} = $local_resources;
3587 __PACKAGE__-
>register_method({
3588 name
=> 'migrate_vm',
3589 path
=> '{vmid}/migrate',
3593 description
=> "Migrate virtual machine. Creates a new migration task.",
3595 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3598 additionalProperties
=> 0,
3600 node
=> get_standard_option
('pve-node'),
3601 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3602 target
=> get_standard_option
('pve-node', {
3603 description
=> "Target node.",
3604 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3608 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3613 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3618 enum
=> ['secure', 'insecure'],
3619 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3622 migration_network
=> {
3623 type
=> 'string', format
=> 'CIDR',
3624 description
=> "CIDR of the (sub) network that is used for migration.",
3627 "with-local-disks" => {
3629 description
=> "Enable live storage migration for local disk",
3632 targetstorage
=> get_standard_option
('pve-targetstorage', {
3633 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3636 description
=> "Override I/O bandwidth limit (in KiB/s).",
3640 default => 'migrate limit from datacenter or storage config',
3646 description
=> "the task ID.",
3651 my $rpcenv = PVE
::RPCEnvironment
::get
();
3652 my $authuser = $rpcenv->get_user();
3654 my $target = extract_param
($param, 'target');
3656 my $localnode = PVE
::INotify
::nodename
();
3657 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3659 PVE
::Cluster
::check_cfs_quorum
();
3661 PVE
::Cluster
::check_node_exists
($target);
3663 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3665 my $vmid = extract_param
($param, 'vmid');
3667 raise_param_exc
({ force
=> "Only root may use this option." })
3668 if $param->{force
} && $authuser ne 'root@pam';
3670 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3671 if $param->{migration_type
} && $authuser ne 'root@pam';
3673 # allow root only until better network permissions are available
3674 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3675 if $param->{migration_network
} && $authuser ne 'root@pam';
3678 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3680 # try to detect errors early
3682 PVE
::QemuConfig-
>check_lock($conf);
3684 if (PVE
::QemuServer
::check_running
($vmid)) {
3685 die "can't migrate running VM without --online\n" if !$param->{online
};
3687 my $repl_conf = PVE
::ReplicationConfig-
>new();
3688 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3689 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3690 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3691 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3692 "target. Use 'force' to override.\n";
3695 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3696 $param->{online
} = 0;
3699 my $storecfg = PVE
::Storage
::config
();
3701 if (my $targetstorage = $param->{targetstorage
}) {
3702 my $check_storage = sub {
3703 my ($target_sid) = @_;
3704 PVE
::Storage
::storage_check_enabled
($storecfg, $target_sid, $target);
3705 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3706 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3707 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3708 if !$scfg->{content
}->{images
};
3711 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3712 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3715 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3716 if !defined($storagemap->{identity
});
3718 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3719 $check_storage->($target_sid);
3722 $check_storage->($storagemap->{default})
3723 if $storagemap->{default};
3725 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3726 if $storagemap->{identity
};
3728 $param->{storagemap
} = $storagemap;
3730 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3733 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3738 print "Requesting HA migration for VM $vmid to node $target\n";
3740 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3741 PVE
::Tools
::run_command
($cmd);
3745 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3750 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3754 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3757 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3762 __PACKAGE__-
>register_method({
3764 path
=> '{vmid}/monitor',
3768 description
=> "Execute Qemu monitor commands.",
3770 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3771 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3774 additionalProperties
=> 0,
3776 node
=> get_standard_option
('pve-node'),
3777 vmid
=> get_standard_option
('pve-vmid'),
3780 description
=> "The monitor command.",
3784 returns
=> { type
=> 'string'},
3788 my $rpcenv = PVE
::RPCEnvironment
::get
();
3789 my $authuser = $rpcenv->get_user();
3792 my $command = shift;
3793 return $command =~ m/^\s*info(\s+|$)/
3794 || $command =~ m/^\s*help\s*$/;
3797 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3798 if !&$is_ro($param->{command
});
3800 my $vmid = $param->{vmid
};
3802 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3806 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3808 $res = "ERROR: $@" if $@;
3813 __PACKAGE__-
>register_method({
3814 name
=> 'resize_vm',
3815 path
=> '{vmid}/resize',
3819 description
=> "Extend volume size.",
3821 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3824 additionalProperties
=> 0,
3826 node
=> get_standard_option
('pve-node'),
3827 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3828 skiplock
=> get_standard_option
('skiplock'),
3831 description
=> "The disk you want to resize.",
3832 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3836 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3837 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.",
3841 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3847 returns
=> { type
=> 'null'},
3851 my $rpcenv = PVE
::RPCEnvironment
::get
();
3853 my $authuser = $rpcenv->get_user();
3855 my $node = extract_param
($param, 'node');
3857 my $vmid = extract_param
($param, 'vmid');
3859 my $digest = extract_param
($param, 'digest');
3861 my $disk = extract_param
($param, 'disk');
3863 my $sizestr = extract_param
($param, 'size');
3865 my $skiplock = extract_param
($param, 'skiplock');
3866 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3867 if $skiplock && $authuser ne 'root@pam';
3869 my $storecfg = PVE
::Storage
::config
();
3871 my $updatefn = sub {
3873 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3875 die "checksum missmatch (file change by other user?)\n"
3876 if $digest && $digest ne $conf->{digest
};
3877 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3879 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3881 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3883 my (undef, undef, undef, undef, undef, undef, $format) =
3884 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3886 die "can't resize volume: $disk if snapshot exists\n"
3887 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3889 my $volid = $drive->{file
};
3891 die "disk '$disk' has no associated volume\n" if !$volid;
3893 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3895 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3897 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3899 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3900 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3902 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3904 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3905 my ($ext, $newsize, $unit) = ($1, $2, $4);
3908 $newsize = $newsize * 1024;
3909 } elsif ($unit eq 'M') {
3910 $newsize = $newsize * 1024 * 1024;
3911 } elsif ($unit eq 'G') {
3912 $newsize = $newsize * 1024 * 1024 * 1024;
3913 } elsif ($unit eq 'T') {
3914 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3917 $newsize += $size if $ext;
3918 $newsize = int($newsize);
3920 die "shrinking disks is not supported\n" if $newsize < $size;
3922 return if $size == $newsize;
3924 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3926 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3928 $drive->{size
} = $newsize;
3929 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3931 PVE
::QemuConfig-
>write_config($vmid, $conf);
3934 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3938 __PACKAGE__-
>register_method({
3939 name
=> 'snapshot_list',
3940 path
=> '{vmid}/snapshot',
3942 description
=> "List all snapshots.",
3944 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3947 protected
=> 1, # qemu pid files are only readable by root
3949 additionalProperties
=> 0,
3951 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3952 node
=> get_standard_option
('pve-node'),
3961 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3965 description
=> "Snapshot includes RAM.",
3970 description
=> "Snapshot description.",
3974 description
=> "Snapshot creation time",
3976 renderer
=> 'timestamp',
3980 description
=> "Parent snapshot identifier.",
3986 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3991 my $vmid = $param->{vmid
};
3993 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3994 my $snaphash = $conf->{snapshots
} || {};
3998 foreach my $name (keys %$snaphash) {
3999 my $d = $snaphash->{$name};
4002 snaptime
=> $d->{snaptime
} || 0,
4003 vmstate
=> $d->{vmstate
} ?
1 : 0,
4004 description
=> $d->{description
} || '',
4006 $item->{parent
} = $d->{parent
} if $d->{parent
};
4007 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4011 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4014 digest
=> $conf->{digest
},
4015 running
=> $running,
4016 description
=> "You are here!",
4018 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4020 push @$res, $current;
4025 __PACKAGE__-
>register_method({
4027 path
=> '{vmid}/snapshot',
4031 description
=> "Snapshot a VM.",
4033 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4036 additionalProperties
=> 0,
4038 node
=> get_standard_option
('pve-node'),
4039 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4040 snapname
=> get_standard_option
('pve-snapshot-name'),
4044 description
=> "Save the vmstate",
4049 description
=> "A textual description or comment.",
4055 description
=> "the task ID.",
4060 my $rpcenv = PVE
::RPCEnvironment
::get
();
4062 my $authuser = $rpcenv->get_user();
4064 my $node = extract_param
($param, 'node');
4066 my $vmid = extract_param
($param, 'vmid');
4068 my $snapname = extract_param
($param, 'snapname');
4070 die "unable to use snapshot name 'current' (reserved name)\n"
4071 if $snapname eq 'current';
4073 die "unable to use snapshot name 'pending' (reserved name)\n"
4074 if lc($snapname) eq 'pending';
4077 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4078 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4079 $param->{description
});
4082 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4085 __PACKAGE__-
>register_method({
4086 name
=> 'snapshot_cmd_idx',
4087 path
=> '{vmid}/snapshot/{snapname}',
4094 additionalProperties
=> 0,
4096 vmid
=> get_standard_option
('pve-vmid'),
4097 node
=> get_standard_option
('pve-node'),
4098 snapname
=> get_standard_option
('pve-snapshot-name'),
4107 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4114 push @$res, { cmd
=> 'rollback' };
4115 push @$res, { cmd
=> 'config' };
4120 __PACKAGE__-
>register_method({
4121 name
=> 'update_snapshot_config',
4122 path
=> '{vmid}/snapshot/{snapname}/config',
4126 description
=> "Update snapshot metadata.",
4128 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4131 additionalProperties
=> 0,
4133 node
=> get_standard_option
('pve-node'),
4134 vmid
=> get_standard_option
('pve-vmid'),
4135 snapname
=> get_standard_option
('pve-snapshot-name'),
4139 description
=> "A textual description or comment.",
4143 returns
=> { type
=> 'null' },
4147 my $rpcenv = PVE
::RPCEnvironment
::get
();
4149 my $authuser = $rpcenv->get_user();
4151 my $vmid = extract_param
($param, 'vmid');
4153 my $snapname = extract_param
($param, 'snapname');
4155 return if !defined($param->{description
});
4157 my $updatefn = sub {
4159 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4161 PVE
::QemuConfig-
>check_lock($conf);
4163 my $snap = $conf->{snapshots
}->{$snapname};
4165 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4167 $snap->{description
} = $param->{description
} if defined($param->{description
});
4169 PVE
::QemuConfig-
>write_config($vmid, $conf);
4172 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4177 __PACKAGE__-
>register_method({
4178 name
=> 'get_snapshot_config',
4179 path
=> '{vmid}/snapshot/{snapname}/config',
4182 description
=> "Get snapshot configuration",
4184 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4187 additionalProperties
=> 0,
4189 node
=> get_standard_option
('pve-node'),
4190 vmid
=> get_standard_option
('pve-vmid'),
4191 snapname
=> get_standard_option
('pve-snapshot-name'),
4194 returns
=> { type
=> "object" },
4198 my $rpcenv = PVE
::RPCEnvironment
::get
();
4200 my $authuser = $rpcenv->get_user();
4202 my $vmid = extract_param
($param, 'vmid');
4204 my $snapname = extract_param
($param, 'snapname');
4206 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4208 my $snap = $conf->{snapshots
}->{$snapname};
4210 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4215 __PACKAGE__-
>register_method({
4217 path
=> '{vmid}/snapshot/{snapname}/rollback',
4221 description
=> "Rollback VM state to specified snapshot.",
4223 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4226 additionalProperties
=> 0,
4228 node
=> get_standard_option
('pve-node'),
4229 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4230 snapname
=> get_standard_option
('pve-snapshot-name'),
4235 description
=> "the task ID.",
4240 my $rpcenv = PVE
::RPCEnvironment
::get
();
4242 my $authuser = $rpcenv->get_user();
4244 my $node = extract_param
($param, 'node');
4246 my $vmid = extract_param
($param, 'vmid');
4248 my $snapname = extract_param
($param, 'snapname');
4251 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4252 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4256 # hold migration lock, this makes sure that nobody create replication snapshots
4257 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4260 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4263 __PACKAGE__-
>register_method({
4264 name
=> 'delsnapshot',
4265 path
=> '{vmid}/snapshot/{snapname}',
4269 description
=> "Delete a VM snapshot.",
4271 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4274 additionalProperties
=> 0,
4276 node
=> get_standard_option
('pve-node'),
4277 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4278 snapname
=> get_standard_option
('pve-snapshot-name'),
4282 description
=> "For removal from config file, even if removing disk snapshots fails.",
4288 description
=> "the task ID.",
4293 my $rpcenv = PVE
::RPCEnvironment
::get
();
4295 my $authuser = $rpcenv->get_user();
4297 my $node = extract_param
($param, 'node');
4299 my $vmid = extract_param
($param, 'vmid');
4301 my $snapname = extract_param
($param, 'snapname');
4304 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4305 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4308 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4311 __PACKAGE__-
>register_method({
4313 path
=> '{vmid}/template',
4317 description
=> "Create a Template.",
4319 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4320 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4323 additionalProperties
=> 0,
4325 node
=> get_standard_option
('pve-node'),
4326 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4330 description
=> "If you want to convert only 1 disk to base image.",
4331 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4336 returns
=> { type
=> 'null'},
4340 my $rpcenv = PVE
::RPCEnvironment
::get
();
4342 my $authuser = $rpcenv->get_user();
4344 my $node = extract_param
($param, 'node');
4346 my $vmid = extract_param
($param, 'vmid');
4348 my $disk = extract_param
($param, 'disk');
4350 my $updatefn = sub {
4352 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4354 PVE
::QemuConfig-
>check_lock($conf);
4356 die "unable to create template, because VM contains snapshots\n"
4357 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4359 die "you can't convert a template to a template\n"
4360 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4362 die "you can't convert a VM to template if VM is running\n"
4363 if PVE
::QemuServer
::check_running
($vmid);
4366 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4369 $conf->{template
} = 1;
4370 PVE
::QemuConfig-
>write_config($vmid, $conf);
4372 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4375 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4379 __PACKAGE__-
>register_method({
4380 name
=> 'cloudinit_generated_config_dump',
4381 path
=> '{vmid}/cloudinit/dump',
4384 description
=> "Get automatically generated cloudinit config.",
4386 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4389 additionalProperties
=> 0,
4391 node
=> get_standard_option
('pve-node'),
4392 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4394 description
=> 'Config type.',
4396 enum
=> ['user', 'network', 'meta'],
4406 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4408 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});