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 # default legacy boot order implies all cdroms anyway
1363 # append new CD drives to bootorder to mark them bootable
1364 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1365 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1366 push @bootorder, $opt;
1367 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1368 $modified->{boot
} = 1;
1371 } elsif ($opt =~ m/^serial\d+/) {
1372 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1373 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1374 } elsif ($authuser ne 'root@pam') {
1375 die "only root can modify '$opt' config for real devices\n";
1377 $conf->{pending
}->{$opt} = $param->{$opt};
1378 } elsif ($opt =~ m/^usb\d+/) {
1379 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1380 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1381 } elsif ($authuser ne 'root@pam') {
1382 die "only root can modify '$opt' config for real devices\n";
1384 $conf->{pending
}->{$opt} = $param->{$opt};
1386 $conf->{pending
}->{$opt} = $param->{$opt};
1388 if ($opt eq 'boot') {
1389 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1390 if ($new_bootcfg->{order
}) {
1391 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1392 for my $dev (@devs) {
1393 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1394 my $deleted = grep {$_ eq $dev} @delete;
1395 die "invalid bootorder: device '$dev' does not exist'\n"
1396 if !$exists || $deleted;
1399 # remove legacy boot order settings if new one set
1400 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1401 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1402 if $conf->{bootdisk
};
1406 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1407 PVE
::QemuConfig-
>write_config($vmid, $conf);
1410 # remove pending changes when nothing changed
1411 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1412 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1413 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1415 return if !scalar(keys %{$conf->{pending
}});
1417 my $running = PVE
::QemuServer
::check_running
($vmid);
1419 # apply pending changes
1421 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1425 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1427 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1429 raise_param_exc
($errors) if scalar(keys %$errors);
1438 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1440 if ($background_delay) {
1442 # Note: It would be better to do that in the Event based HTTPServer
1443 # to avoid blocking call to sleep.
1445 my $end_time = time() + $background_delay;
1447 my $task = PVE
::Tools
::upid_decode
($upid);
1450 while (time() < $end_time) {
1451 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1453 sleep(1); # this gets interrupted when child process ends
1457 my $status = PVE
::Tools
::upid_read_status
($upid);
1458 return if !PVE
::Tools
::upid_status_is_error
($status);
1467 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1470 my $vm_config_perm_list = [
1475 'VM.Config.Network',
1477 'VM.Config.Options',
1478 'VM.Config.Cloudinit',
1481 __PACKAGE__-
>register_method({
1482 name
=> 'update_vm_async',
1483 path
=> '{vmid}/config',
1487 description
=> "Set virtual machine options (asynchrounous API).",
1489 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1492 additionalProperties
=> 0,
1493 properties
=> PVE
::QemuServer
::json_config_properties
(
1495 node
=> get_standard_option
('pve-node'),
1496 vmid
=> get_standard_option
('pve-vmid'),
1497 skiplock
=> get_standard_option
('skiplock'),
1499 type
=> 'string', format
=> 'pve-configid-list',
1500 description
=> "A list of settings you want to delete.",
1504 type
=> 'string', format
=> 'pve-configid-list',
1505 description
=> "Revert a pending change.",
1510 description
=> $opt_force_description,
1512 requires
=> 'delete',
1516 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1520 background_delay
=> {
1522 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1533 code
=> $update_vm_api,
1536 __PACKAGE__-
>register_method({
1537 name
=> 'update_vm',
1538 path
=> '{vmid}/config',
1542 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1544 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1547 additionalProperties
=> 0,
1548 properties
=> PVE
::QemuServer
::json_config_properties
(
1550 node
=> get_standard_option
('pve-node'),
1551 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1552 skiplock
=> get_standard_option
('skiplock'),
1554 type
=> 'string', format
=> 'pve-configid-list',
1555 description
=> "A list of settings you want to delete.",
1559 type
=> 'string', format
=> 'pve-configid-list',
1560 description
=> "Revert a pending change.",
1565 description
=> $opt_force_description,
1567 requires
=> 'delete',
1571 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1577 returns
=> { type
=> 'null' },
1580 &$update_vm_api($param, 1);
1585 __PACKAGE__-
>register_method({
1586 name
=> 'destroy_vm',
1591 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1592 ." and firewall rules",
1594 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1597 additionalProperties
=> 0,
1599 node
=> get_standard_option
('pve-node'),
1600 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1601 skiplock
=> get_standard_option
('skiplock'),
1604 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1607 'destroy-unreferenced-disks' => {
1609 description
=> "If set, destroy additionally all disks not referenced in the config"
1610 ." but with a matching VMID from all enabled storages.",
1622 my $rpcenv = PVE
::RPCEnvironment
::get
();
1623 my $authuser = $rpcenv->get_user();
1624 my $vmid = $param->{vmid
};
1626 my $skiplock = $param->{skiplock
};
1627 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1628 if $skiplock && $authuser ne 'root@pam';
1630 my $early_checks = sub {
1632 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1633 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1635 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1637 if (!$param->{purge
}) {
1638 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1640 # don't allow destroy if with replication jobs but no purge param
1641 my $repl_conf = PVE
::ReplicationConfig-
>new();
1642 $repl_conf->check_for_existing_jobs($vmid);
1645 die "VM $vmid is running - destroy failed\n"
1646 if PVE
::QemuServer
::check_running
($vmid);
1656 my $storecfg = PVE
::Storage
::config
();
1658 syslog
('info', "destroy VM $vmid: $upid\n");
1659 PVE
::QemuConfig-
>lock_config($vmid, sub {
1660 # repeat, config might have changed
1661 my $ha_managed = $early_checks->();
1663 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1665 PVE
::QemuServer
::destroy_vm
(
1668 $skiplock, { lock => 'destroyed' },
1669 $purge_unreferenced,
1672 PVE
::AccessControl
::remove_vm_access
($vmid);
1673 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1674 if ($param->{purge
}) {
1675 print "purging VM $vmid from related configurations..\n";
1676 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1677 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1680 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1681 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1685 # only now remove the zombie config, else we can have reuse race
1686 PVE
::QemuConfig-
>destroy_config($vmid);
1690 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1693 __PACKAGE__-
>register_method({
1695 path
=> '{vmid}/unlink',
1699 description
=> "Unlink/delete disk images.",
1701 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1704 additionalProperties
=> 0,
1706 node
=> get_standard_option
('pve-node'),
1707 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1709 type
=> 'string', format
=> 'pve-configid-list',
1710 description
=> "A list of disk IDs you want to delete.",
1714 description
=> $opt_force_description,
1719 returns
=> { type
=> 'null'},
1723 $param->{delete} = extract_param
($param, 'idlist');
1725 __PACKAGE__-
>update_vm($param);
1730 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1731 my $gen_rand_chars = sub {
1734 die "invalid length $length" if $length < 1;
1736 my $min = ord('!'); # first printable ascii
1738 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1739 die "failed to generate random bytes!\n"
1742 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1749 __PACKAGE__-
>register_method({
1751 path
=> '{vmid}/vncproxy',
1755 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1757 description
=> "Creates a TCP VNC proxy connections.",
1759 additionalProperties
=> 0,
1761 node
=> get_standard_option
('pve-node'),
1762 vmid
=> get_standard_option
('pve-vmid'),
1766 description
=> "starts websockify instead of vncproxy",
1768 'generate-password' => {
1772 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1777 additionalProperties
=> 0,
1779 user
=> { type
=> 'string' },
1780 ticket
=> { type
=> 'string' },
1783 description
=> "Returned if requested with 'generate-password' param."
1784 ." Consists of printable ASCII characters ('!' .. '~').",
1787 cert
=> { type
=> 'string' },
1788 port
=> { type
=> 'integer' },
1789 upid
=> { type
=> 'string' },
1795 my $rpcenv = PVE
::RPCEnvironment
::get
();
1797 my $authuser = $rpcenv->get_user();
1799 my $vmid = $param->{vmid
};
1800 my $node = $param->{node
};
1801 my $websocket = $param->{websocket
};
1803 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1807 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1808 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1811 my $authpath = "/vms/$vmid";
1813 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1814 my $password = $ticket;
1815 if ($param->{'generate-password'}) {
1816 $password = $gen_rand_chars->(8);
1819 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1825 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1826 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1827 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1828 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1829 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1831 $family = PVE
::Tools
::get_host_address_family
($node);
1834 my $port = PVE
::Tools
::next_vnc_port
($family);
1841 syslog
('info', "starting vnc proxy $upid\n");
1845 if (defined($serial)) {
1847 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1849 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1850 '-timeout', $timeout, '-authpath', $authpath,
1851 '-perm', 'Sys.Console'];
1853 if ($param->{websocket
}) {
1854 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1855 push @$cmd, '-notls', '-listen', 'localhost';
1858 push @$cmd, '-c', @$remcmd, @$termcmd;
1860 PVE
::Tools
::run_command
($cmd);
1864 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1866 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1868 my $sock = IO
::Socket
::IP-
>new(
1873 GetAddrInfoFlags
=> 0,
1874 ) or die "failed to create socket: $!\n";
1875 # Inside the worker we shouldn't have any previous alarms
1876 # running anyway...:
1878 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1880 accept(my $cli, $sock) or die "connection failed: $!\n";
1883 if (PVE
::Tools
::run_command
($cmd,
1884 output
=> '>&'.fileno($cli),
1885 input
=> '<&'.fileno($cli),
1888 die "Failed to run vncproxy.\n";
1895 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1897 PVE
::Tools
::wait_for_vnc_port
($port);
1906 $res->{password
} = $password if $param->{'generate-password'};
1911 __PACKAGE__-
>register_method({
1912 name
=> 'termproxy',
1913 path
=> '{vmid}/termproxy',
1917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1919 description
=> "Creates a TCP proxy connections.",
1921 additionalProperties
=> 0,
1923 node
=> get_standard_option
('pve-node'),
1924 vmid
=> get_standard_option
('pve-vmid'),
1928 enum
=> [qw(serial0 serial1 serial2 serial3)],
1929 description
=> "opens a serial terminal (defaults to display)",
1934 additionalProperties
=> 0,
1936 user
=> { type
=> 'string' },
1937 ticket
=> { type
=> 'string' },
1938 port
=> { type
=> 'integer' },
1939 upid
=> { type
=> 'string' },
1945 my $rpcenv = PVE
::RPCEnvironment
::get
();
1947 my $authuser = $rpcenv->get_user();
1949 my $vmid = $param->{vmid
};
1950 my $node = $param->{node
};
1951 my $serial = $param->{serial
};
1953 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1955 if (!defined($serial)) {
1957 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1958 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1962 my $authpath = "/vms/$vmid";
1964 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1969 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1970 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1971 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1972 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1973 push @$remcmd, '--';
1975 $family = PVE
::Tools
::get_host_address_family
($node);
1978 my $port = PVE
::Tools
::next_vnc_port
($family);
1980 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1981 push @$termcmd, '-iface', $serial if $serial;
1986 syslog
('info', "starting qemu termproxy $upid\n");
1988 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1989 '--perm', 'VM.Console', '--'];
1990 push @$cmd, @$remcmd, @$termcmd;
1992 PVE
::Tools
::run_command
($cmd);
1995 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1997 PVE
::Tools
::wait_for_vnc_port
($port);
2007 __PACKAGE__-
>register_method({
2008 name
=> 'vncwebsocket',
2009 path
=> '{vmid}/vncwebsocket',
2012 description
=> "You also need to pass a valid ticket (vncticket).",
2013 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2015 description
=> "Opens a weksocket for VNC traffic.",
2017 additionalProperties
=> 0,
2019 node
=> get_standard_option
('pve-node'),
2020 vmid
=> get_standard_option
('pve-vmid'),
2022 description
=> "Ticket from previous call to vncproxy.",
2027 description
=> "Port number returned by previous vncproxy call.",
2037 port
=> { type
=> 'string' },
2043 my $rpcenv = PVE
::RPCEnvironment
::get
();
2045 my $authuser = $rpcenv->get_user();
2047 my $vmid = $param->{vmid
};
2048 my $node = $param->{node
};
2050 my $authpath = "/vms/$vmid";
2052 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2054 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2056 # Note: VNC ports are acessible from outside, so we do not gain any
2057 # security if we verify that $param->{port} belongs to VM $vmid. This
2058 # check is done by verifying the VNC ticket (inside VNC protocol).
2060 my $port = $param->{port
};
2062 return { port
=> $port };
2065 __PACKAGE__-
>register_method({
2066 name
=> 'spiceproxy',
2067 path
=> '{vmid}/spiceproxy',
2072 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2074 description
=> "Returns a SPICE configuration to connect to the VM.",
2076 additionalProperties
=> 0,
2078 node
=> get_standard_option
('pve-node'),
2079 vmid
=> get_standard_option
('pve-vmid'),
2080 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2083 returns
=> get_standard_option
('remote-viewer-config'),
2087 my $rpcenv = PVE
::RPCEnvironment
::get
();
2089 my $authuser = $rpcenv->get_user();
2091 my $vmid = $param->{vmid
};
2092 my $node = $param->{node
};
2093 my $proxy = $param->{proxy
};
2095 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2096 my $title = "VM $vmid";
2097 $title .= " - ". $conf->{name
} if $conf->{name
};
2099 my $port = PVE
::QemuServer
::spice_port
($vmid);
2101 my ($ticket, undef, $remote_viewer_config) =
2102 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2104 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2105 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2107 return $remote_viewer_config;
2110 __PACKAGE__-
>register_method({
2112 path
=> '{vmid}/status',
2115 description
=> "Directory index",
2120 additionalProperties
=> 0,
2122 node
=> get_standard_option
('pve-node'),
2123 vmid
=> get_standard_option
('pve-vmid'),
2131 subdir
=> { type
=> 'string' },
2134 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2140 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2143 { subdir
=> 'current' },
2144 { subdir
=> 'start' },
2145 { subdir
=> 'stop' },
2146 { subdir
=> 'reset' },
2147 { subdir
=> 'shutdown' },
2148 { subdir
=> 'suspend' },
2149 { subdir
=> 'reboot' },
2155 __PACKAGE__-
>register_method({
2156 name
=> 'vm_status',
2157 path
=> '{vmid}/status/current',
2160 protected
=> 1, # qemu pid files are only readable by root
2161 description
=> "Get virtual machine status.",
2163 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2166 additionalProperties
=> 0,
2168 node
=> get_standard_option
('pve-node'),
2169 vmid
=> get_standard_option
('pve-vmid'),
2175 %$PVE::QemuServer
::vmstatus_return_properties
,
2177 description
=> "HA manager service status.",
2181 description
=> "Qemu VGA configuration supports spice.",
2186 description
=> "Qemu GuestAgent enabled in config.",
2196 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2198 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2199 my $status = $vmstatus->{$param->{vmid
}};
2201 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2203 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2204 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2209 __PACKAGE__-
>register_method({
2211 path
=> '{vmid}/status/start',
2215 description
=> "Start virtual machine.",
2217 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2220 additionalProperties
=> 0,
2222 node
=> get_standard_option
('pve-node'),
2223 vmid
=> get_standard_option
('pve-vmid',
2224 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2225 skiplock
=> get_standard_option
('skiplock'),
2226 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2227 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2230 enum
=> ['secure', 'insecure'],
2231 description
=> "Migration traffic is encrypted using an SSH " .
2232 "tunnel by default. On secure, completely private networks " .
2233 "this can be disabled to increase performance.",
2236 migration_network
=> {
2237 type
=> 'string', format
=> 'CIDR',
2238 description
=> "CIDR of the (sub) network that is used for migration.",
2241 machine
=> get_standard_option
('pve-qemu-machine'),
2243 description
=> "Override QEMU's -cpu argument with the given string.",
2247 targetstorage
=> get_standard_option
('pve-targetstorage'),
2249 description
=> "Wait maximal timeout seconds.",
2252 default => 'max(30, vm memory in GiB)',
2263 my $rpcenv = PVE
::RPCEnvironment
::get
();
2264 my $authuser = $rpcenv->get_user();
2266 my $node = extract_param
($param, 'node');
2267 my $vmid = extract_param
($param, 'vmid');
2268 my $timeout = extract_param
($param, 'timeout');
2270 my $machine = extract_param
($param, 'machine');
2271 my $force_cpu = extract_param
($param, 'force-cpu');
2273 my $get_root_param = sub {
2274 my $value = extract_param
($param, $_[0]);
2275 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2276 if $value && $authuser ne 'root@pam';
2280 my $stateuri = $get_root_param->('stateuri');
2281 my $skiplock = $get_root_param->('skiplock');
2282 my $migratedfrom = $get_root_param->('migratedfrom');
2283 my $migration_type = $get_root_param->('migration_type');
2284 my $migration_network = $get_root_param->('migration_network');
2285 my $targetstorage = $get_root_param->('targetstorage');
2289 if ($targetstorage) {
2290 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2292 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2293 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2297 # read spice ticket from STDIN
2299 my $nbd_protocol_version = 0;
2300 my $replicated_volumes = {};
2301 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2302 while (defined(my $line = <STDIN
>)) {
2304 if ($line =~ m/^spice_ticket: (.+)$/) {
2306 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2307 $nbd_protocol_version = $1;
2308 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2309 $replicated_volumes->{$1} = 1;
2311 # fallback for old source node
2312 $spice_ticket = $line;
2317 PVE
::Cluster
::check_cfs_quorum
();
2319 my $storecfg = PVE
::Storage
::config
();
2321 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2325 print "Requesting HA start for VM $vmid\n";
2327 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2328 PVE
::Tools
::run_command
($cmd);
2332 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2339 syslog
('info', "start VM $vmid: $upid\n");
2341 my $migrate_opts = {
2342 migratedfrom
=> $migratedfrom,
2343 spice_ticket
=> $spice_ticket,
2344 network
=> $migration_network,
2345 type
=> $migration_type,
2346 storagemap
=> $storagemap,
2347 nbd_proto_version
=> $nbd_protocol_version,
2348 replicated_volumes
=> $replicated_volumes,
2352 statefile
=> $stateuri,
2353 skiplock
=> $skiplock,
2354 forcemachine
=> $machine,
2355 timeout
=> $timeout,
2356 forcecpu
=> $force_cpu,
2359 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2363 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2367 __PACKAGE__-
>register_method({
2369 path
=> '{vmid}/status/stop',
2373 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2374 "is akin to pulling the power plug of a running computer and may damage the VM data",
2376 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2379 additionalProperties
=> 0,
2381 node
=> get_standard_option
('pve-node'),
2382 vmid
=> get_standard_option
('pve-vmid',
2383 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2384 skiplock
=> get_standard_option
('skiplock'),
2385 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2387 description
=> "Wait maximal timeout seconds.",
2393 description
=> "Do not deactivate storage volumes.",
2406 my $rpcenv = PVE
::RPCEnvironment
::get
();
2407 my $authuser = $rpcenv->get_user();
2409 my $node = extract_param
($param, 'node');
2410 my $vmid = extract_param
($param, 'vmid');
2412 my $skiplock = extract_param
($param, 'skiplock');
2413 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2414 if $skiplock && $authuser ne 'root@pam';
2416 my $keepActive = extract_param
($param, 'keepActive');
2417 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2418 if $keepActive && $authuser ne 'root@pam';
2420 my $migratedfrom = extract_param
($param, 'migratedfrom');
2421 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2422 if $migratedfrom && $authuser ne 'root@pam';
2425 my $storecfg = PVE
::Storage
::config
();
2427 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2432 print "Requesting HA stop for VM $vmid\n";
2434 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2435 PVE
::Tools
::run_command
($cmd);
2439 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2445 syslog
('info', "stop VM $vmid: $upid\n");
2447 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2448 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2452 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2456 __PACKAGE__-
>register_method({
2458 path
=> '{vmid}/status/reset',
2462 description
=> "Reset virtual machine.",
2464 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2467 additionalProperties
=> 0,
2469 node
=> get_standard_option
('pve-node'),
2470 vmid
=> get_standard_option
('pve-vmid',
2471 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2472 skiplock
=> get_standard_option
('skiplock'),
2481 my $rpcenv = PVE
::RPCEnvironment
::get
();
2483 my $authuser = $rpcenv->get_user();
2485 my $node = extract_param
($param, 'node');
2487 my $vmid = extract_param
($param, 'vmid');
2489 my $skiplock = extract_param
($param, 'skiplock');
2490 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2491 if $skiplock && $authuser ne 'root@pam';
2493 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2498 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2503 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2506 __PACKAGE__-
>register_method({
2507 name
=> 'vm_shutdown',
2508 path
=> '{vmid}/status/shutdown',
2512 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2513 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2515 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2518 additionalProperties
=> 0,
2520 node
=> get_standard_option
('pve-node'),
2521 vmid
=> get_standard_option
('pve-vmid',
2522 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2523 skiplock
=> get_standard_option
('skiplock'),
2525 description
=> "Wait maximal timeout seconds.",
2531 description
=> "Make sure the VM stops.",
2537 description
=> "Do not deactivate storage volumes.",
2550 my $rpcenv = PVE
::RPCEnvironment
::get
();
2551 my $authuser = $rpcenv->get_user();
2553 my $node = extract_param
($param, 'node');
2554 my $vmid = extract_param
($param, 'vmid');
2556 my $skiplock = extract_param
($param, 'skiplock');
2557 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2558 if $skiplock && $authuser ne 'root@pam';
2560 my $keepActive = extract_param
($param, 'keepActive');
2561 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2562 if $keepActive && $authuser ne 'root@pam';
2564 my $storecfg = PVE
::Storage
::config
();
2568 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2569 # otherwise, we will infer a shutdown command, but run into the timeout,
2570 # then when the vm is resumed, it will instantly shutdown
2572 # checking the qmp status here to get feedback to the gui/cli/api
2573 # and the status query should not take too long
2574 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2575 if ($param->{forceStop
}) {
2576 warn "VM is paused - stop instead of shutdown\n";
2579 die "VM is paused - cannot shutdown\n";
2583 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2585 my $timeout = $param->{timeout
} // 60;
2589 print "Requesting HA stop for VM $vmid\n";
2591 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2592 PVE
::Tools
::run_command
($cmd);
2596 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2603 syslog
('info', "shutdown VM $vmid: $upid\n");
2605 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2606 $shutdown, $param->{forceStop
}, $keepActive);
2610 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2614 __PACKAGE__-
>register_method({
2615 name
=> 'vm_reboot',
2616 path
=> '{vmid}/status/reboot',
2620 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2622 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2625 additionalProperties
=> 0,
2627 node
=> get_standard_option
('pve-node'),
2628 vmid
=> get_standard_option
('pve-vmid',
2629 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2631 description
=> "Wait maximal timeout seconds for the shutdown.",
2644 my $rpcenv = PVE
::RPCEnvironment
::get
();
2645 my $authuser = $rpcenv->get_user();
2647 my $node = extract_param
($param, 'node');
2648 my $vmid = extract_param
($param, 'vmid');
2650 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2652 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2657 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2658 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2662 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2665 __PACKAGE__-
>register_method({
2666 name
=> 'vm_suspend',
2667 path
=> '{vmid}/status/suspend',
2671 description
=> "Suspend virtual machine.",
2673 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2674 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2675 " on the storage for the vmstate.",
2676 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2679 additionalProperties
=> 0,
2681 node
=> get_standard_option
('pve-node'),
2682 vmid
=> get_standard_option
('pve-vmid',
2683 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2684 skiplock
=> get_standard_option
('skiplock'),
2689 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2691 statestorage
=> get_standard_option
('pve-storage-id', {
2692 description
=> "The storage for the VM state",
2693 requires
=> 'todisk',
2695 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2705 my $rpcenv = PVE
::RPCEnvironment
::get
();
2706 my $authuser = $rpcenv->get_user();
2708 my $node = extract_param
($param, 'node');
2709 my $vmid = extract_param
($param, 'vmid');
2711 my $todisk = extract_param
($param, 'todisk') // 0;
2713 my $statestorage = extract_param
($param, 'statestorage');
2715 my $skiplock = extract_param
($param, 'skiplock');
2716 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2717 if $skiplock && $authuser ne 'root@pam';
2719 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2721 die "Cannot suspend HA managed VM to disk\n"
2722 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2724 # early check for storage permission, for better user feedback
2726 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2728 if (!$statestorage) {
2729 # get statestorage from config if none is given
2730 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2731 my $storecfg = PVE
::Storage
::config
();
2732 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2735 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2741 syslog
('info', "suspend VM $vmid: $upid\n");
2743 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2748 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2749 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2752 __PACKAGE__-
>register_method({
2753 name
=> 'vm_resume',
2754 path
=> '{vmid}/status/resume',
2758 description
=> "Resume virtual machine.",
2760 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2763 additionalProperties
=> 0,
2765 node
=> get_standard_option
('pve-node'),
2766 vmid
=> get_standard_option
('pve-vmid',
2767 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2768 skiplock
=> get_standard_option
('skiplock'),
2769 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2779 my $rpcenv = PVE
::RPCEnvironment
::get
();
2781 my $authuser = $rpcenv->get_user();
2783 my $node = extract_param
($param, 'node');
2785 my $vmid = extract_param
($param, 'vmid');
2787 my $skiplock = extract_param
($param, 'skiplock');
2788 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2789 if $skiplock && $authuser ne 'root@pam';
2791 my $nocheck = extract_param
($param, 'nocheck');
2792 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2793 if $nocheck && $authuser ne 'root@pam';
2795 my $to_disk_suspended;
2797 PVE
::QemuConfig-
>lock_config($vmid, sub {
2798 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2799 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2803 die "VM $vmid not running\n"
2804 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2809 syslog
('info', "resume VM $vmid: $upid\n");
2811 if (!$to_disk_suspended) {
2812 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2814 my $storecfg = PVE
::Storage
::config
();
2815 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2821 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2824 __PACKAGE__-
>register_method({
2825 name
=> 'vm_sendkey',
2826 path
=> '{vmid}/sendkey',
2830 description
=> "Send key event to virtual machine.",
2832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2835 additionalProperties
=> 0,
2837 node
=> get_standard_option
('pve-node'),
2838 vmid
=> get_standard_option
('pve-vmid',
2839 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2840 skiplock
=> get_standard_option
('skiplock'),
2842 description
=> "The key (qemu monitor encoding).",
2847 returns
=> { type
=> 'null'},
2851 my $rpcenv = PVE
::RPCEnvironment
::get
();
2853 my $authuser = $rpcenv->get_user();
2855 my $node = extract_param
($param, 'node');
2857 my $vmid = extract_param
($param, 'vmid');
2859 my $skiplock = extract_param
($param, 'skiplock');
2860 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2861 if $skiplock && $authuser ne 'root@pam';
2863 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2868 __PACKAGE__-
>register_method({
2869 name
=> 'vm_feature',
2870 path
=> '{vmid}/feature',
2874 description
=> "Check if feature for virtual machine is available.",
2876 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2879 additionalProperties
=> 0,
2881 node
=> get_standard_option
('pve-node'),
2882 vmid
=> get_standard_option
('pve-vmid'),
2884 description
=> "Feature to check.",
2886 enum
=> [ 'snapshot', 'clone', 'copy' ],
2888 snapname
=> get_standard_option
('pve-snapshot-name', {
2896 hasFeature
=> { type
=> 'boolean' },
2899 items
=> { type
=> 'string' },
2906 my $node = extract_param
($param, 'node');
2908 my $vmid = extract_param
($param, 'vmid');
2910 my $snapname = extract_param
($param, 'snapname');
2912 my $feature = extract_param
($param, 'feature');
2914 my $running = PVE
::QemuServer
::check_running
($vmid);
2916 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2919 my $snap = $conf->{snapshots
}->{$snapname};
2920 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2923 my $storecfg = PVE
::Storage
::config
();
2925 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2926 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2929 hasFeature
=> $hasFeature,
2930 nodes
=> [ keys %$nodelist ],
2934 __PACKAGE__-
>register_method({
2936 path
=> '{vmid}/clone',
2940 description
=> "Create a copy of virtual machine/template.",
2942 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2943 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2944 "'Datastore.AllocateSpace' on any used storage.",
2947 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2949 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2950 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2955 additionalProperties
=> 0,
2957 node
=> get_standard_option
('pve-node'),
2958 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2959 newid
=> get_standard_option
('pve-vmid', {
2960 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2961 description
=> 'VMID for the clone.' }),
2964 type
=> 'string', format
=> 'dns-name',
2965 description
=> "Set a name for the new VM.",
2970 description
=> "Description for the new VM.",
2974 type
=> 'string', format
=> 'pve-poolid',
2975 description
=> "Add the new VM to the specified pool.",
2977 snapname
=> get_standard_option
('pve-snapshot-name', {
2980 storage
=> get_standard_option
('pve-storage-id', {
2981 description
=> "Target storage for full clone.",
2985 description
=> "Target format for file storage. Only valid for full clone.",
2988 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2993 description
=> "Create a full copy of all disks. This is always done when " .
2994 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2996 target
=> get_standard_option
('pve-node', {
2997 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3001 description
=> "Override I/O bandwidth limit (in KiB/s).",
3005 default => 'clone limit from datacenter or storage config',
3015 my $rpcenv = PVE
::RPCEnvironment
::get
();
3016 my $authuser = $rpcenv->get_user();
3018 my $node = extract_param
($param, 'node');
3019 my $vmid = extract_param
($param, 'vmid');
3020 my $newid = extract_param
($param, 'newid');
3021 my $pool = extract_param
($param, 'pool');
3022 $rpcenv->check_pool_exist($pool) if defined($pool);
3024 my $snapname = extract_param
($param, 'snapname');
3025 my $storage = extract_param
($param, 'storage');
3026 my $format = extract_param
($param, 'format');
3027 my $target = extract_param
($param, 'target');
3029 my $localnode = PVE
::INotify
::nodename
();
3031 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3035 PVE
::Cluster
::check_node_exists
($target) if $target;
3037 my $storecfg = PVE
::Storage
::config
();
3040 # check if storage is enabled on local node
3041 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3043 # check if storage is available on target node
3044 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3045 # clone only works if target storage is shared
3046 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3047 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3051 PVE
::Cluster
::check_cfs_quorum
();
3053 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3056 # do all tests after lock but before forking worker - if possible
3058 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3059 PVE
::QemuConfig-
>check_lock($conf);
3061 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3062 die "unexpected state change\n" if $verify_running != $running;
3064 die "snapshot '$snapname' does not exist\n"
3065 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3067 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3069 die "parameter 'storage' not allowed for linked clones\n"
3070 if defined($storage) && !$full;
3072 die "parameter 'format' not allowed for linked clones\n"
3073 if defined($format) && !$full;
3075 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3077 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3079 die "can't clone VM to node '$target' (VM uses local storage)\n"
3080 if $target && !$sharedvm;
3082 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3083 die "unable to create VM $newid: config file already exists\n"
3086 my $newconf = { lock => 'clone' };
3091 foreach my $opt (keys %$oldconf) {
3092 my $value = $oldconf->{$opt};
3094 # do not copy snapshot related info
3095 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3096 $opt eq 'vmstate' || $opt eq 'snapstate';
3098 # no need to copy unused images, because VMID(owner) changes anyways
3099 next if $opt =~ m/^unused\d+$/;
3101 # always change MAC! address
3102 if ($opt =~ m/^net(\d+)$/) {
3103 my $net = PVE
::QemuServer
::parse_net
($value);
3104 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3105 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3106 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3107 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3108 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3109 die "unable to parse drive options for '$opt'\n" if !$drive;
3110 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3111 $newconf->{$opt} = $value; # simply copy configuration
3113 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3114 die "Full clone feature is not supported for drive '$opt'\n"
3115 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3116 $fullclone->{$opt} = 1;
3118 # not full means clone instead of copy
3119 die "Linked clone feature is not supported for drive '$opt'\n"
3120 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3122 $drives->{$opt} = $drive;
3123 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3124 push @$vollist, $drive->{file
};
3127 # copy everything else
3128 $newconf->{$opt} = $value;
3132 # auto generate a new uuid
3133 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3134 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3135 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3136 # auto generate a new vmgenid only if the option was set for template
3137 if ($newconf->{vmgenid
}) {
3138 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3141 delete $newconf->{template
};
3143 if ($param->{name
}) {
3144 $newconf->{name
} = $param->{name
};
3146 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3149 if ($param->{description
}) {
3150 $newconf->{description
} = $param->{description
};
3153 # create empty/temp config - this fails if VM already exists on other node
3154 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3155 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3160 my $newvollist = [];
3167 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3169 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3171 my $bwlimit = extract_param
($param, 'bwlimit');
3173 my $total_jobs = scalar(keys %{$drives});
3176 foreach my $opt (sort keys %$drives) {
3177 my $drive = $drives->{$opt};
3178 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3179 my $completion = $skipcomplete ?
'skip' : 'complete';
3181 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3182 my $storage_list = [ $src_sid ];
3183 push @$storage_list, $storage if defined($storage);
3184 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3186 my $newdrive = PVE
::QemuServer
::clone_disk
(
3205 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3207 PVE
::QemuConfig-
>write_config($newid, $newconf);
3211 delete $newconf->{lock};
3213 # do not write pending changes
3214 if (my @changes = keys %{$newconf->{pending
}}) {
3215 my $pending = join(',', @changes);
3216 warn "found pending changes for '$pending', discarding for clone\n";
3217 delete $newconf->{pending
};
3220 PVE
::QemuConfig-
>write_config($newid, $newconf);
3223 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3224 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3225 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3227 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3228 die "Failed to move config to node '$target' - rename failed: $!\n"
3229 if !rename($conffile, $newconffile);
3232 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3235 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3236 sleep 1; # some storage like rbd need to wait before release volume - really?
3238 foreach my $volid (@$newvollist) {
3239 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3243 PVE
::Firewall
::remove_vmfw_conf
($newid);
3245 unlink $conffile; # avoid races -> last thing before die
3247 die "clone failed: $err";
3253 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3255 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3258 # Aquire exclusive lock lock for $newid
3259 my $lock_target_vm = sub {
3260 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3263 # exclusive lock if VM is running - else shared lock is enough;
3265 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3267 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3271 __PACKAGE__-
>register_method({
3272 name
=> 'move_vm_disk',
3273 path
=> '{vmid}/move_disk',
3277 description
=> "Move volume to different storage.",
3279 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3281 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3282 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3286 additionalProperties
=> 0,
3288 node
=> get_standard_option
('pve-node'),
3289 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3292 description
=> "The disk you want to move.",
3293 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3295 storage
=> get_standard_option
('pve-storage-id', {
3296 description
=> "Target storage.",
3297 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3301 description
=> "Target Format.",
3302 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3307 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3313 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3318 description
=> "Override I/O bandwidth limit (in KiB/s).",
3322 default => 'move limit from datacenter or storage config',
3328 description
=> "the task ID.",
3333 my $rpcenv = PVE
::RPCEnvironment
::get
();
3334 my $authuser = $rpcenv->get_user();
3336 my $node = extract_param
($param, 'node');
3337 my $vmid = extract_param
($param, 'vmid');
3338 my $digest = extract_param
($param, 'digest');
3339 my $disk = extract_param
($param, 'disk');
3340 my $storeid = extract_param
($param, 'storage');
3341 my $format = extract_param
($param, 'format');
3343 my $storecfg = PVE
::Storage
::config
();
3345 my $updatefn = sub {
3346 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3347 PVE
::QemuConfig-
>check_lock($conf);
3349 die "VM config checksum missmatch (file change by other user?)\n"
3350 if $digest && $digest ne $conf->{digest
};
3352 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3354 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3356 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3357 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3359 my $old_volid = $drive->{file
};
3361 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3362 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3366 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3367 (!$format || !$oldfmt || $oldfmt eq $format);
3369 # this only checks snapshots because $disk is passed!
3370 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3371 die "you can't move a disk with snapshots and delete the source\n"
3372 if $snapshotted && $param->{delete};
3374 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3376 my $running = PVE
::QemuServer
::check_running
($vmid);
3378 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3381 my $newvollist = [];
3387 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3389 warn "moving disk with snapshots, snapshots will not be moved!\n"
3392 my $bwlimit = extract_param
($param, 'bwlimit');
3393 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3395 my $newdrive = PVE
::QemuServer
::clone_disk
(
3413 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3415 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3417 # convert moved disk to base if part of template
3418 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3419 if PVE
::QemuConfig-
>is_template($conf);
3421 PVE
::QemuConfig-
>write_config($vmid, $conf);
3423 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3424 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3425 eval { mon_cmd
($vmid, "guest-fstrim") };
3429 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3430 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3436 foreach my $volid (@$newvollist) {
3437 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3440 die "storage migration failed: $err";
3443 if ($param->{delete}) {
3445 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3446 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3452 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3455 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3458 my $check_vm_disks_local = sub {
3459 my ($storecfg, $vmconf, $vmid) = @_;
3461 my $local_disks = {};
3463 # add some more information to the disks e.g. cdrom
3464 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3465 my ($volid, $attr) = @_;
3467 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3469 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3470 return if $scfg->{shared
};
3472 # The shared attr here is just a special case where the vdisk
3473 # is marked as shared manually
3474 return if $attr->{shared
};
3475 return if $attr->{cdrom
} and $volid eq "none";
3477 if (exists $local_disks->{$volid}) {
3478 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3480 $local_disks->{$volid} = $attr;
3481 # ensure volid is present in case it's needed
3482 $local_disks->{$volid}->{volid
} = $volid;
3486 return $local_disks;
3489 __PACKAGE__-
>register_method({
3490 name
=> 'migrate_vm_precondition',
3491 path
=> '{vmid}/migrate',
3495 description
=> "Get preconditions for migration.",
3497 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3500 additionalProperties
=> 0,
3502 node
=> get_standard_option
('pve-node'),
3503 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3504 target
=> get_standard_option
('pve-node', {
3505 description
=> "Target node.",
3506 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3514 running
=> { type
=> 'boolean' },
3518 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3520 not_allowed_nodes
=> {
3523 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3527 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3529 local_resources
=> {
3531 description
=> "List local resources e.g. pci, usb"
3538 my $rpcenv = PVE
::RPCEnvironment
::get
();
3540 my $authuser = $rpcenv->get_user();
3542 PVE
::Cluster
::check_cfs_quorum
();
3546 my $vmid = extract_param
($param, 'vmid');
3547 my $target = extract_param
($param, 'target');
3548 my $localnode = PVE
::INotify
::nodename
();
3552 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3553 my $storecfg = PVE
::Storage
::config
();
3556 # try to detect errors early
3557 PVE
::QemuConfig-
>check_lock($vmconf);
3559 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3561 # if vm is not running, return target nodes where local storage is available
3562 # for offline migration
3563 if (!$res->{running
}) {
3564 $res->{allowed_nodes
} = [];
3565 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3566 delete $checked_nodes->{$localnode};
3568 foreach my $node (keys %$checked_nodes) {
3569 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3570 push @{$res->{allowed_nodes
}}, $node;
3574 $res->{not_allowed_nodes
} = $checked_nodes;
3578 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3579 $res->{local_disks
} = [ values %$local_disks ];;
3581 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3583 $res->{local_resources
} = $local_resources;
3590 __PACKAGE__-
>register_method({
3591 name
=> 'migrate_vm',
3592 path
=> '{vmid}/migrate',
3596 description
=> "Migrate virtual machine. Creates a new migration task.",
3598 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3601 additionalProperties
=> 0,
3603 node
=> get_standard_option
('pve-node'),
3604 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3605 target
=> get_standard_option
('pve-node', {
3606 description
=> "Target node.",
3607 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3611 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3616 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3621 enum
=> ['secure', 'insecure'],
3622 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3625 migration_network
=> {
3626 type
=> 'string', format
=> 'CIDR',
3627 description
=> "CIDR of the (sub) network that is used for migration.",
3630 "with-local-disks" => {
3632 description
=> "Enable live storage migration for local disk",
3635 targetstorage
=> get_standard_option
('pve-targetstorage', {
3636 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3639 description
=> "Override I/O bandwidth limit (in KiB/s).",
3643 default => 'migrate limit from datacenter or storage config',
3649 description
=> "the task ID.",
3654 my $rpcenv = PVE
::RPCEnvironment
::get
();
3655 my $authuser = $rpcenv->get_user();
3657 my $target = extract_param
($param, 'target');
3659 my $localnode = PVE
::INotify
::nodename
();
3660 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3662 PVE
::Cluster
::check_cfs_quorum
();
3664 PVE
::Cluster
::check_node_exists
($target);
3666 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3668 my $vmid = extract_param
($param, 'vmid');
3670 raise_param_exc
({ force
=> "Only root may use this option." })
3671 if $param->{force
} && $authuser ne 'root@pam';
3673 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3674 if $param->{migration_type
} && $authuser ne 'root@pam';
3676 # allow root only until better network permissions are available
3677 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3678 if $param->{migration_network
} && $authuser ne 'root@pam';
3681 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3683 # try to detect errors early
3685 PVE
::QemuConfig-
>check_lock($conf);
3687 if (PVE
::QemuServer
::check_running
($vmid)) {
3688 die "can't migrate running VM without --online\n" if !$param->{online
};
3690 my $repl_conf = PVE
::ReplicationConfig-
>new();
3691 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3692 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3693 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3694 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3695 "target. Use 'force' to override.\n";
3698 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3699 $param->{online
} = 0;
3702 my $storecfg = PVE
::Storage
::config
();
3704 if (my $targetstorage = $param->{targetstorage
}) {
3705 my $check_storage = sub {
3706 my ($target_sid) = @_;
3707 PVE
::Storage
::storage_check_enabled
($storecfg, $target_sid, $target);
3708 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3709 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3710 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3711 if !$scfg->{content
}->{images
};
3714 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3715 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3718 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3719 if !defined($storagemap->{identity
});
3721 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3722 $check_storage->($target_sid);
3725 $check_storage->($storagemap->{default})
3726 if $storagemap->{default};
3728 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3729 if $storagemap->{identity
};
3731 $param->{storagemap
} = $storagemap;
3733 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3736 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3741 print "Requesting HA migration for VM $vmid to node $target\n";
3743 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3744 PVE
::Tools
::run_command
($cmd);
3748 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3753 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3757 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3760 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3765 __PACKAGE__-
>register_method({
3767 path
=> '{vmid}/monitor',
3771 description
=> "Execute Qemu monitor commands.",
3773 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3774 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3777 additionalProperties
=> 0,
3779 node
=> get_standard_option
('pve-node'),
3780 vmid
=> get_standard_option
('pve-vmid'),
3783 description
=> "The monitor command.",
3787 returns
=> { type
=> 'string'},
3791 my $rpcenv = PVE
::RPCEnvironment
::get
();
3792 my $authuser = $rpcenv->get_user();
3795 my $command = shift;
3796 return $command =~ m/^\s*info(\s+|$)/
3797 || $command =~ m/^\s*help\s*$/;
3800 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3801 if !&$is_ro($param->{command
});
3803 my $vmid = $param->{vmid
};
3805 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3809 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3811 $res = "ERROR: $@" if $@;
3816 __PACKAGE__-
>register_method({
3817 name
=> 'resize_vm',
3818 path
=> '{vmid}/resize',
3822 description
=> "Extend volume size.",
3824 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3827 additionalProperties
=> 0,
3829 node
=> get_standard_option
('pve-node'),
3830 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3831 skiplock
=> get_standard_option
('skiplock'),
3834 description
=> "The disk you want to resize.",
3835 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3839 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3840 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.",
3844 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3850 returns
=> { type
=> 'null'},
3854 my $rpcenv = PVE
::RPCEnvironment
::get
();
3856 my $authuser = $rpcenv->get_user();
3858 my $node = extract_param
($param, 'node');
3860 my $vmid = extract_param
($param, 'vmid');
3862 my $digest = extract_param
($param, 'digest');
3864 my $disk = extract_param
($param, 'disk');
3866 my $sizestr = extract_param
($param, 'size');
3868 my $skiplock = extract_param
($param, 'skiplock');
3869 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3870 if $skiplock && $authuser ne 'root@pam';
3872 my $storecfg = PVE
::Storage
::config
();
3874 my $updatefn = sub {
3876 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3878 die "checksum missmatch (file change by other user?)\n"
3879 if $digest && $digest ne $conf->{digest
};
3880 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3882 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3884 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3886 my (undef, undef, undef, undef, undef, undef, $format) =
3887 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3889 die "can't resize volume: $disk if snapshot exists\n"
3890 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3892 my $volid = $drive->{file
};
3894 die "disk '$disk' has no associated volume\n" if !$volid;
3896 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3898 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3900 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3902 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3903 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3905 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3907 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3908 my ($ext, $newsize, $unit) = ($1, $2, $4);
3911 $newsize = $newsize * 1024;
3912 } elsif ($unit eq 'M') {
3913 $newsize = $newsize * 1024 * 1024;
3914 } elsif ($unit eq 'G') {
3915 $newsize = $newsize * 1024 * 1024 * 1024;
3916 } elsif ($unit eq 'T') {
3917 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3920 $newsize += $size if $ext;
3921 $newsize = int($newsize);
3923 die "shrinking disks is not supported\n" if $newsize < $size;
3925 return if $size == $newsize;
3927 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3929 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3931 $drive->{size
} = $newsize;
3932 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3934 PVE
::QemuConfig-
>write_config($vmid, $conf);
3937 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3941 __PACKAGE__-
>register_method({
3942 name
=> 'snapshot_list',
3943 path
=> '{vmid}/snapshot',
3945 description
=> "List all snapshots.",
3947 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3950 protected
=> 1, # qemu pid files are only readable by root
3952 additionalProperties
=> 0,
3954 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3955 node
=> get_standard_option
('pve-node'),
3964 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3968 description
=> "Snapshot includes RAM.",
3973 description
=> "Snapshot description.",
3977 description
=> "Snapshot creation time",
3979 renderer
=> 'timestamp',
3983 description
=> "Parent snapshot identifier.",
3989 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3994 my $vmid = $param->{vmid
};
3996 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3997 my $snaphash = $conf->{snapshots
} || {};
4001 foreach my $name (keys %$snaphash) {
4002 my $d = $snaphash->{$name};
4005 snaptime
=> $d->{snaptime
} || 0,
4006 vmstate
=> $d->{vmstate
} ?
1 : 0,
4007 description
=> $d->{description
} || '',
4009 $item->{parent
} = $d->{parent
} if $d->{parent
};
4010 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4014 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4017 digest
=> $conf->{digest
},
4018 running
=> $running,
4019 description
=> "You are here!",
4021 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4023 push @$res, $current;
4028 __PACKAGE__-
>register_method({
4030 path
=> '{vmid}/snapshot',
4034 description
=> "Snapshot a VM.",
4036 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4039 additionalProperties
=> 0,
4041 node
=> get_standard_option
('pve-node'),
4042 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4043 snapname
=> get_standard_option
('pve-snapshot-name'),
4047 description
=> "Save the vmstate",
4052 description
=> "A textual description or comment.",
4058 description
=> "the task ID.",
4063 my $rpcenv = PVE
::RPCEnvironment
::get
();
4065 my $authuser = $rpcenv->get_user();
4067 my $node = extract_param
($param, 'node');
4069 my $vmid = extract_param
($param, 'vmid');
4071 my $snapname = extract_param
($param, 'snapname');
4073 die "unable to use snapshot name 'current' (reserved name)\n"
4074 if $snapname eq 'current';
4076 die "unable to use snapshot name 'pending' (reserved name)\n"
4077 if lc($snapname) eq 'pending';
4080 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4081 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4082 $param->{description
});
4085 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4088 __PACKAGE__-
>register_method({
4089 name
=> 'snapshot_cmd_idx',
4090 path
=> '{vmid}/snapshot/{snapname}',
4097 additionalProperties
=> 0,
4099 vmid
=> get_standard_option
('pve-vmid'),
4100 node
=> get_standard_option
('pve-node'),
4101 snapname
=> get_standard_option
('pve-snapshot-name'),
4110 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4117 push @$res, { cmd
=> 'rollback' };
4118 push @$res, { cmd
=> 'config' };
4123 __PACKAGE__-
>register_method({
4124 name
=> 'update_snapshot_config',
4125 path
=> '{vmid}/snapshot/{snapname}/config',
4129 description
=> "Update snapshot metadata.",
4131 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4134 additionalProperties
=> 0,
4136 node
=> get_standard_option
('pve-node'),
4137 vmid
=> get_standard_option
('pve-vmid'),
4138 snapname
=> get_standard_option
('pve-snapshot-name'),
4142 description
=> "A textual description or comment.",
4146 returns
=> { type
=> 'null' },
4150 my $rpcenv = PVE
::RPCEnvironment
::get
();
4152 my $authuser = $rpcenv->get_user();
4154 my $vmid = extract_param
($param, 'vmid');
4156 my $snapname = extract_param
($param, 'snapname');
4158 return if !defined($param->{description
});
4160 my $updatefn = sub {
4162 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4164 PVE
::QemuConfig-
>check_lock($conf);
4166 my $snap = $conf->{snapshots
}->{$snapname};
4168 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4170 $snap->{description
} = $param->{description
} if defined($param->{description
});
4172 PVE
::QemuConfig-
>write_config($vmid, $conf);
4175 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4180 __PACKAGE__-
>register_method({
4181 name
=> 'get_snapshot_config',
4182 path
=> '{vmid}/snapshot/{snapname}/config',
4185 description
=> "Get snapshot configuration",
4187 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4190 additionalProperties
=> 0,
4192 node
=> get_standard_option
('pve-node'),
4193 vmid
=> get_standard_option
('pve-vmid'),
4194 snapname
=> get_standard_option
('pve-snapshot-name'),
4197 returns
=> { type
=> "object" },
4201 my $rpcenv = PVE
::RPCEnvironment
::get
();
4203 my $authuser = $rpcenv->get_user();
4205 my $vmid = extract_param
($param, 'vmid');
4207 my $snapname = extract_param
($param, 'snapname');
4209 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4211 my $snap = $conf->{snapshots
}->{$snapname};
4213 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4218 __PACKAGE__-
>register_method({
4220 path
=> '{vmid}/snapshot/{snapname}/rollback',
4224 description
=> "Rollback VM state to specified snapshot.",
4226 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4229 additionalProperties
=> 0,
4231 node
=> get_standard_option
('pve-node'),
4232 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4233 snapname
=> get_standard_option
('pve-snapshot-name'),
4238 description
=> "the task ID.",
4243 my $rpcenv = PVE
::RPCEnvironment
::get
();
4245 my $authuser = $rpcenv->get_user();
4247 my $node = extract_param
($param, 'node');
4249 my $vmid = extract_param
($param, 'vmid');
4251 my $snapname = extract_param
($param, 'snapname');
4254 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4255 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4259 # hold migration lock, this makes sure that nobody create replication snapshots
4260 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4263 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4266 __PACKAGE__-
>register_method({
4267 name
=> 'delsnapshot',
4268 path
=> '{vmid}/snapshot/{snapname}',
4272 description
=> "Delete a VM snapshot.",
4274 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4277 additionalProperties
=> 0,
4279 node
=> get_standard_option
('pve-node'),
4280 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4281 snapname
=> get_standard_option
('pve-snapshot-name'),
4285 description
=> "For removal from config file, even if removing disk snapshots fails.",
4291 description
=> "the task ID.",
4296 my $rpcenv = PVE
::RPCEnvironment
::get
();
4298 my $authuser = $rpcenv->get_user();
4300 my $node = extract_param
($param, 'node');
4302 my $vmid = extract_param
($param, 'vmid');
4304 my $snapname = extract_param
($param, 'snapname');
4307 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4308 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4311 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4314 __PACKAGE__-
>register_method({
4316 path
=> '{vmid}/template',
4320 description
=> "Create a Template.",
4322 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4323 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4326 additionalProperties
=> 0,
4328 node
=> get_standard_option
('pve-node'),
4329 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4333 description
=> "If you want to convert only 1 disk to base image.",
4334 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4341 description
=> "the task ID.",
4346 my $rpcenv = PVE
::RPCEnvironment
::get
();
4348 my $authuser = $rpcenv->get_user();
4350 my $node = extract_param
($param, 'node');
4352 my $vmid = extract_param
($param, 'vmid');
4354 my $disk = extract_param
($param, 'disk');
4356 my $updatefn = sub {
4358 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4360 PVE
::QemuConfig-
>check_lock($conf);
4362 die "unable to create template, because VM contains snapshots\n"
4363 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4365 die "you can't convert a template to a template\n"
4366 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4368 die "you can't convert a VM to template if VM is running\n"
4369 if PVE
::QemuServer
::check_running
($vmid);
4372 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4375 $conf->{template
} = 1;
4376 PVE
::QemuConfig-
>write_config($vmid, $conf);
4378 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4381 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4384 __PACKAGE__-
>register_method({
4385 name
=> 'cloudinit_generated_config_dump',
4386 path
=> '{vmid}/cloudinit/dump',
4389 description
=> "Get automatically generated cloudinit config.",
4391 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4394 additionalProperties
=> 0,
4396 node
=> get_standard_option
('pve-node'),
4397 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4399 description
=> 'Config type.',
4401 enum
=> ['user', 'network', 'meta'],
4411 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4413 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});