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);
27 use PVE
::QemuServer
::Machine
;
29 use PVE
::RPCEnvironment
;
30 use PVE
::AccessControl
;
34 use PVE
::API2
::Firewall
::VM
;
35 use PVE
::API2
::Qemu
::Agent
;
36 use PVE
::VZDump
::Plugin
;
37 use PVE
::DataCenterConfig
;
41 if (!$ENV{PVE_GENERATING_DOCS
}) {
42 require PVE
::HA
::Env
::PVE2
;
43 import PVE
::HA
::Env
::PVE2
;
44 require PVE
::HA
::Config
;
45 import PVE
::HA
::Config
;
49 use Data
::Dumper
; # fixme: remove
51 use base
qw(PVE::RESTHandler);
53 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.";
55 my $resolve_cdrom_alias = sub {
58 if (my $value = $param->{cdrom
}) {
59 $value .= ",media=cdrom" if $value !~ m/media=/;
60 $param->{ide2
} = $value;
61 delete $param->{cdrom
};
65 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
66 my $check_storage_access = sub {
67 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
69 PVE
::QemuConfig-
>foreach_volume($settings, sub {
70 my ($ds, $drive) = @_;
72 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
74 my $volid = $drive->{file
};
75 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
77 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
79 } elsif ($isCDROM && ($volid eq 'cdrom')) {
80 $rpcenv->check($authuser, "/", ['Sys.Console']);
81 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
82 my ($storeid, $size) = ($2 || $default_storage, $3);
83 die "no storage ID specified (and no default storage)\n" if !$storeid;
84 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
85 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
86 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
87 if !$scfg->{content
}->{images
};
89 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
93 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
94 if defined($settings->{vmstatestorage
});
97 my $check_storage_access_clone = sub {
98 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
102 PVE
::QemuConfig-
>foreach_volume($conf, sub {
103 my ($ds, $drive) = @_;
105 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
107 my $volid = $drive->{file
};
109 return if !$volid || $volid eq 'none';
112 if ($volid eq 'cdrom') {
113 $rpcenv->check($authuser, "/", ['Sys.Console']);
115 # we simply allow access
116 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
117 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
118 $sharedvm = 0 if !$scfg->{shared
};
122 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
123 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
124 $sharedvm = 0 if !$scfg->{shared
};
126 $sid = $storage if $storage;
127 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
131 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
132 if defined($conf->{vmstatestorage
});
137 # Note: $pool is only needed when creating a VM, because pool permissions
138 # are automatically inherited if VM already exists inside a pool.
139 my $create_disks = sub {
140 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
147 my ($ds, $disk) = @_;
149 my $volid = $disk->{file
};
150 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
152 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
153 delete $disk->{size
};
154 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
155 } elsif (defined($volname) && $volname eq 'cloudinit') {
156 $storeid = $storeid // $default_storage;
157 die "no storage ID specified (and no default storage)\n" if !$storeid;
158 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
159 my $name = "vm-$vmid-cloudinit";
163 $fmt = $disk->{format
} // "qcow2";
166 $fmt = $disk->{format
} // "raw";
169 # Initial disk created with 4 MB and aligned to 4MB on regeneration
170 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
171 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
172 $disk->{file
} = $volid;
173 $disk->{media
} = 'cdrom';
174 push @$vollist, $volid;
175 delete $disk->{format
}; # no longer needed
176 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
177 } elsif ($volid =~ $NEW_DISK_RE) {
178 my ($storeid, $size) = ($2 || $default_storage, $3);
179 die "no storage ID specified (and no default storage)\n" if !$storeid;
180 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
181 my $fmt = $disk->{format
} || $defformat;
183 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
186 if ($ds eq 'efidisk0') {
187 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
188 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
189 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
190 } elsif ($ds eq 'tpmstate0') {
191 # swtpm can only use raw volumes, and uses a fixed size
192 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
193 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
195 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
197 push @$vollist, $volid;
198 $disk->{file
} = $volid;
199 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
200 delete $disk->{format
}; # no longer needed
201 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
204 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
206 my $volid_is_new = 1;
209 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
210 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
215 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
217 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
219 die "volume $volid does not exist\n" if !$size;
221 $disk->{size
} = $size;
224 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
228 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
230 # free allocated images on error
232 syslog
('err', "VM $vmid creating disks failed");
233 foreach my $volid (@$vollist) {
234 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
240 # modify vm config if everything went well
241 foreach my $ds (keys %$res) {
242 $conf->{$ds} = $res->{$ds};
248 my $check_cpu_model_access = sub {
249 my ($rpcenv, $authuser, $new, $existing) = @_;
251 return if !defined($new->{cpu
});
253 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
254 return if !$cpu || !$cpu->{cputype
}; # always allow default
255 my $cputype = $cpu->{cputype
};
257 if ($existing && $existing->{cpu
}) {
258 # changing only other settings doesn't require permissions for CPU model
259 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
260 return if $existingCpu->{cputype
} eq $cputype;
263 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
264 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
279 my $memoryoptions = {
285 my $hwtypeoptions = {
298 my $generaloptions = {
305 'migrate_downtime' => 1,
306 'migrate_speed' => 1,
319 my $vmpoweroptions = {
326 'vmstatestorage' => 1,
329 my $cloudinitoptions = {
339 my $check_vm_create_serial_perm = sub {
340 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
342 return 1 if $authuser eq 'root@pam';
344 foreach my $opt (keys %{$param}) {
345 next if $opt !~ m/^serial\d+$/;
347 if ($param->{$opt} eq 'socket') {
348 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
350 die "only root can set '$opt' config for real devices\n";
357 my $check_vm_create_usb_perm = sub {
358 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
360 return 1 if $authuser eq 'root@pam';
362 foreach my $opt (keys %{$param}) {
363 next if $opt !~ m/^usb\d+$/;
365 if ($param->{$opt} =~ m/spice/) {
366 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
368 die "only root can set '$opt' config for real devices\n";
375 my $check_vm_modify_config_perm = sub {
376 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
378 return 1 if $authuser eq 'root@pam';
380 foreach my $opt (@$key_list) {
381 # some checks (e.g., disk, serial port, usb) need to be done somewhere
382 # else, as there the permission can be value dependend
383 next if PVE
::QemuServer
::is_valid_drivename
($opt);
384 next if $opt eq 'cdrom';
385 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
388 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
389 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
390 } elsif ($memoryoptions->{$opt}) {
391 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
392 } elsif ($hwtypeoptions->{$opt}) {
393 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
394 } elsif ($generaloptions->{$opt}) {
395 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
396 # special case for startup since it changes host behaviour
397 if ($opt eq 'startup') {
398 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
400 } elsif ($vmpoweroptions->{$opt}) {
401 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
402 } elsif ($diskoptions->{$opt}) {
403 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
404 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
405 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
406 } elsif ($cloudinitoptions->{$opt}) {
407 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
408 } elsif ($opt eq 'vmstate') {
409 # the user needs Disk and PowerMgmt privileges to change the vmstate
410 # also needs privileges on the storage, that will be checked later
411 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
413 # catches hostpci\d+, args, lock, etc.
414 # new options will be checked here
415 die "only root can set '$opt' config\n";
422 __PACKAGE__-
>register_method({
426 description
=> "Virtual machine index (per node).",
428 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
432 protected
=> 1, # qemu pid files are only readable by root
434 additionalProperties
=> 0,
436 node
=> get_standard_option
('pve-node'),
440 description
=> "Determine the full status of active VMs.",
448 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
450 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
455 my $rpcenv = PVE
::RPCEnvironment
::get
();
456 my $authuser = $rpcenv->get_user();
458 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
461 foreach my $vmid (keys %$vmstatus) {
462 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
464 my $data = $vmstatus->{$vmid};
471 my $parse_restore_archive = sub {
472 my ($storecfg, $archive) = @_;
474 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
476 if (defined($archive_storeid)) {
477 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
478 if ($scfg->{type
} eq 'pbs') {
485 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
493 __PACKAGE__-
>register_method({
497 description
=> "Create or restore a virtual machine.",
499 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
500 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
501 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
502 user
=> 'all', # check inside
507 additionalProperties
=> 0,
508 properties
=> PVE
::QemuServer
::json_config_properties
(
510 node
=> get_standard_option
('pve-node'),
511 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
513 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.",
517 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
519 storage
=> get_standard_option
('pve-storage-id', {
520 description
=> "Default storage.",
522 completion
=> \
&PVE
::QemuServer
::complete_storage
,
527 description
=> "Allow to overwrite existing VM.",
528 requires
=> 'archive',
533 description
=> "Assign a unique random ethernet address.",
534 requires
=> 'archive',
539 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
540 requires
=> 'archive',
544 type
=> 'string', format
=> 'pve-poolid',
545 description
=> "Add the VM to the specified pool.",
548 description
=> "Override I/O bandwidth limit (in KiB/s).",
552 default => 'restore limit from datacenter or storage config',
558 description
=> "Start VM after it was created successfully.",
568 my $rpcenv = PVE
::RPCEnvironment
::get
();
569 my $authuser = $rpcenv->get_user();
571 my $node = extract_param
($param, 'node');
572 my $vmid = extract_param
($param, 'vmid');
574 my $archive = extract_param
($param, 'archive');
575 my $is_restore = !!$archive;
577 my $bwlimit = extract_param
($param, 'bwlimit');
578 my $force = extract_param
($param, 'force');
579 my $pool = extract_param
($param, 'pool');
580 my $start_after_create = extract_param
($param, 'start');
581 my $storage = extract_param
($param, 'storage');
582 my $unique = extract_param
($param, 'unique');
583 my $live_restore = extract_param
($param, 'live-restore');
585 if (defined(my $ssh_keys = $param->{sshkeys
})) {
586 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
587 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
590 PVE
::Cluster
::check_cfs_quorum
();
592 my $filename = PVE
::QemuConfig-
>config_file($vmid);
593 my $storecfg = PVE
::Storage
::config
();
595 if (defined($pool)) {
596 $rpcenv->check_pool_exist($pool);
599 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
600 if defined($storage);
602 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
604 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
606 } elsif ($archive && $force && (-f
$filename) &&
607 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
608 # OK: user has VM.Backup permissions, and want to restore an existing VM
614 &$resolve_cdrom_alias($param);
616 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
618 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
620 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
621 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
623 &$check_cpu_model_access($rpcenv, $authuser, $param);
625 foreach my $opt (keys %$param) {
626 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
627 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
628 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
630 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
631 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
635 PVE
::QemuServer
::add_random_macs
($param);
637 my $keystr = join(' ', keys %$param);
638 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
640 if ($archive eq '-') {
641 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
642 $archive = { type
=> 'pipe' };
644 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
646 $archive = $parse_restore_archive->($storecfg, $archive);
650 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
652 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
653 die "$emsg $@" if $@;
655 my $restored_data = 0;
656 my $restorefn = sub {
657 my $conf = PVE
::QemuConfig-
>load_config($vmid);
659 PVE
::QemuConfig-
>check_protection($conf, $emsg);
661 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
664 my $restore_options = {
669 live
=> $live_restore,
671 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
672 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
674 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
675 } elsif ($archive->{type
} eq 'pbs') {
676 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
678 die "unknown backup archive type\n";
682 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
683 # Convert restored VM to template if backup was VM template
684 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
685 warn "Convert to template.\n";
686 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
691 # ensure no old replication state are exists
692 PVE
::ReplicationState
::delete_guest_states
($vmid);
694 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
696 if ($start_after_create && !$live_restore) {
697 print "Execute autostart\n";
698 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
704 # ensure no old replication state are exists
705 PVE
::ReplicationState
::delete_guest_states
($vmid);
709 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
713 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
715 if (!$conf->{boot
}) {
716 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
717 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
720 # auto generate uuid if user did not specify smbios1 option
721 if (!$conf->{smbios1
}) {
722 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
725 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
726 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
729 my $machine = $conf->{machine
};
730 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
731 # always pin Windows' machine version on create, they get to easily confused
732 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
733 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
737 PVE
::QemuConfig-
>write_config($vmid, $conf);
743 foreach my $volid (@$vollist) {
744 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
750 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
753 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
755 if ($start_after_create) {
756 print "Execute autostart\n";
757 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
762 my ($code, $worker_name);
764 $worker_name = 'qmrestore';
766 eval { $restorefn->() };
768 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
770 if ($restored_data) {
771 warn "error after data was restored, VM disks should be OK but config may "
772 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
774 warn "error before or during data restore, some or all disks were not "
775 ."completely restored. VM $vmid state is NOT cleaned up.\n";
781 $worker_name = 'qmcreate';
783 eval { $createfn->() };
786 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
787 unlink($conffile) or die "failed to remove config file: $!\n";
795 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
798 __PACKAGE__-
>register_method({
803 description
=> "Directory index",
808 additionalProperties
=> 0,
810 node
=> get_standard_option
('pve-node'),
811 vmid
=> get_standard_option
('pve-vmid'),
819 subdir
=> { type
=> 'string' },
822 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
828 { subdir
=> 'config' },
829 { subdir
=> 'pending' },
830 { subdir
=> 'status' },
831 { subdir
=> 'unlink' },
832 { subdir
=> 'vncproxy' },
833 { subdir
=> 'termproxy' },
834 { subdir
=> 'migrate' },
835 { subdir
=> 'resize' },
836 { subdir
=> 'move' },
838 { subdir
=> 'rrddata' },
839 { subdir
=> 'monitor' },
840 { subdir
=> 'agent' },
841 { subdir
=> 'snapshot' },
842 { subdir
=> 'spiceproxy' },
843 { subdir
=> 'sendkey' },
844 { subdir
=> 'firewall' },
850 __PACKAGE__-
>register_method ({
851 subclass
=> "PVE::API2::Firewall::VM",
852 path
=> '{vmid}/firewall',
855 __PACKAGE__-
>register_method ({
856 subclass
=> "PVE::API2::Qemu::Agent",
857 path
=> '{vmid}/agent',
860 __PACKAGE__-
>register_method({
862 path
=> '{vmid}/rrd',
864 protected
=> 1, # fixme: can we avoid that?
866 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
868 description
=> "Read VM RRD statistics (returns PNG)",
870 additionalProperties
=> 0,
872 node
=> get_standard_option
('pve-node'),
873 vmid
=> get_standard_option
('pve-vmid'),
875 description
=> "Specify the time frame you are interested in.",
877 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
880 description
=> "The list of datasources you want to display.",
881 type
=> 'string', format
=> 'pve-configid-list',
884 description
=> "The RRD consolidation function",
886 enum
=> [ 'AVERAGE', 'MAX' ],
894 filename
=> { type
=> 'string' },
900 return PVE
::RRD
::create_rrd_graph
(
901 "pve2-vm/$param->{vmid}", $param->{timeframe
},
902 $param->{ds
}, $param->{cf
});
906 __PACKAGE__-
>register_method({
908 path
=> '{vmid}/rrddata',
910 protected
=> 1, # fixme: can we avoid that?
912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
914 description
=> "Read VM RRD statistics",
916 additionalProperties
=> 0,
918 node
=> get_standard_option
('pve-node'),
919 vmid
=> get_standard_option
('pve-vmid'),
921 description
=> "Specify the time frame you are interested in.",
923 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
926 description
=> "The RRD consolidation function",
928 enum
=> [ 'AVERAGE', 'MAX' ],
943 return PVE
::RRD
::create_rrd_data
(
944 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
948 __PACKAGE__-
>register_method({
950 path
=> '{vmid}/config',
953 description
=> "Get the virtual machine configuration with pending configuration " .
954 "changes applied. Set the 'current' parameter to get the current configuration instead.",
956 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
959 additionalProperties
=> 0,
961 node
=> get_standard_option
('pve-node'),
962 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
964 description
=> "Get current values (instead of pending values).",
969 snapshot
=> get_standard_option
('pve-snapshot-name', {
970 description
=> "Fetch config values from given snapshot.",
973 my ($cmd, $pname, $cur, $args) = @_;
974 PVE
::QemuConfig-
>snapshot_list($args->[0]);
980 description
=> "The VM configuration.",
982 properties
=> PVE
::QemuServer
::json_config_properties
({
985 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
992 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
993 current
=> "cannot use 'snapshot' parameter with 'current'"})
994 if ($param->{snapshot
} && $param->{current
});
997 if ($param->{snapshot
}) {
998 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1000 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1002 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1007 __PACKAGE__-
>register_method({
1008 name
=> 'vm_pending',
1009 path
=> '{vmid}/pending',
1012 description
=> "Get the virtual machine configuration with both current and pending values.",
1014 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1017 additionalProperties
=> 0,
1019 node
=> get_standard_option
('pve-node'),
1020 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1029 description
=> "Configuration option name.",
1033 description
=> "Current value.",
1038 description
=> "Pending value.",
1043 description
=> "Indicates a pending delete request if present and not 0. " .
1044 "The value 2 indicates a force-delete request.",
1056 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1058 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1060 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1061 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1063 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1066 # POST/PUT {vmid}/config implementation
1068 # The original API used PUT (idempotent) an we assumed that all operations
1069 # are fast. But it turned out that almost any configuration change can
1070 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1071 # time to complete and have side effects (not idempotent).
1073 # The new implementation uses POST and forks a worker process. We added
1074 # a new option 'background_delay'. If specified we wait up to
1075 # 'background_delay' second for the worker task to complete. It returns null
1076 # if the task is finished within that time, else we return the UPID.
1078 my $update_vm_api = sub {
1079 my ($param, $sync) = @_;
1081 my $rpcenv = PVE
::RPCEnvironment
::get
();
1083 my $authuser = $rpcenv->get_user();
1085 my $node = extract_param
($param, 'node');
1087 my $vmid = extract_param
($param, 'vmid');
1089 my $digest = extract_param
($param, 'digest');
1091 my $background_delay = extract_param
($param, 'background_delay');
1093 if (defined(my $cipassword = $param->{cipassword
})) {
1094 # Same logic as in cloud-init (but with the regex fixed...)
1095 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1096 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1099 my @paramarr = (); # used for log message
1100 foreach my $key (sort keys %$param) {
1101 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1102 push @paramarr, "-$key", $value;
1105 my $skiplock = extract_param
($param, 'skiplock');
1106 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1107 if $skiplock && $authuser ne 'root@pam';
1109 my $delete_str = extract_param
($param, 'delete');
1111 my $revert_str = extract_param
($param, 'revert');
1113 my $force = extract_param
($param, 'force');
1115 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1116 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1117 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1120 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1122 my $storecfg = PVE
::Storage
::config
();
1124 my $defaults = PVE
::QemuServer
::load_defaults
();
1126 &$resolve_cdrom_alias($param);
1128 # now try to verify all parameters
1131 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1132 if (!PVE
::QemuServer
::option_exists
($opt)) {
1133 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1136 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1137 "-revert $opt' at the same time" })
1138 if defined($param->{$opt});
1140 $revert->{$opt} = 1;
1144 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1145 $opt = 'ide2' if $opt eq 'cdrom';
1147 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1148 "-delete $opt' at the same time" })
1149 if defined($param->{$opt});
1151 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1152 "-revert $opt' at the same time" })
1155 if (!PVE
::QemuServer
::option_exists
($opt)) {
1156 raise_param_exc
({ delete => "unknown option '$opt'" });
1162 my $repl_conf = PVE
::ReplicationConfig-
>new();
1163 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1164 my $check_replication = sub {
1166 return if !$is_replicated;
1167 my $volid = $drive->{file
};
1168 return if !$volid || !($drive->{replicate
}//1);
1169 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1171 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1172 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1173 if !defined($storeid);
1175 return if defined($volname) && $volname eq 'cloudinit';
1178 if ($volid =~ $NEW_DISK_RE) {
1180 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1182 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1184 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1185 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1186 return if $scfg->{shared
};
1187 die "cannot add non-replicatable volume to a replicated VM\n";
1190 foreach my $opt (keys %$param) {
1191 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1192 # cleanup drive path
1193 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1194 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1195 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1196 $check_replication->($drive);
1197 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1198 } elsif ($opt =~ m/^net(\d+)$/) {
1200 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1201 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1202 } elsif ($opt eq 'vmgenid') {
1203 if ($param->{$opt} eq '1') {
1204 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1206 } elsif ($opt eq 'hookscript') {
1207 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1208 raise_param_exc
({ $opt => $@ }) if $@;
1212 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1214 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1216 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1218 my $updatefn = sub {
1220 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1222 die "checksum missmatch (file change by other user?)\n"
1223 if $digest && $digest ne $conf->{digest
};
1225 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1227 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1228 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1229 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1230 delete $conf->{lock}; # for check lock check, not written out
1231 push @delete, 'lock'; # this is the real deal to write it out
1233 push @delete, 'runningmachine' if $conf->{runningmachine
};
1234 push @delete, 'runningcpu' if $conf->{runningcpu
};
1237 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1239 foreach my $opt (keys %$revert) {
1240 if (defined($conf->{$opt})) {
1241 $param->{$opt} = $conf->{$opt};
1242 } elsif (defined($conf->{pending
}->{$opt})) {
1247 if ($param->{memory
} || defined($param->{balloon
})) {
1248 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1249 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1251 die "balloon value too large (must be smaller than assigned memory)\n"
1252 if $balloon && $balloon > $maxmem;
1255 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1259 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1261 # write updates to pending section
1263 my $modified = {}; # record what $option we modify
1266 if (my $boot = $conf->{boot
}) {
1267 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1268 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1270 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1272 my $check_drive_perms = sub {
1273 my ($opt, $val) = @_;
1274 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1275 # FIXME: cloudinit: CDROM or Disk?
1276 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1277 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1279 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1283 foreach my $opt (@delete) {
1284 $modified->{$opt} = 1;
1285 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1287 # value of what we want to delete, independent if pending or not
1288 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1289 if (!defined($val)) {
1290 warn "cannot delete '$opt' - not set in current configuration!\n";
1291 $modified->{$opt} = 0;
1294 my $is_pending_val = defined($conf->{pending
}->{$opt});
1295 delete $conf->{pending
}->{$opt};
1297 # remove from bootorder if necessary
1298 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1299 @bootorder = grep {$_ ne $opt} @bootorder;
1300 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1301 $modified->{boot
} = 1;
1304 if ($opt =~ m/^unused/) {
1305 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1306 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1307 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1308 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1309 delete $conf->{$opt};
1310 PVE
::QemuConfig-
>write_config($vmid, $conf);
1312 } elsif ($opt eq 'vmstate') {
1313 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1314 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1315 delete $conf->{$opt};
1316 PVE
::QemuConfig-
>write_config($vmid, $conf);
1318 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1319 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1320 $check_drive_perms->($opt, $val);
1321 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1323 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1324 PVE
::QemuConfig-
>write_config($vmid, $conf);
1325 } elsif ($opt =~ m/^serial\d+$/) {
1326 if ($val eq 'socket') {
1327 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1328 } elsif ($authuser ne 'root@pam') {
1329 die "only root can delete '$opt' config for real devices\n";
1331 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1332 PVE
::QemuConfig-
>write_config($vmid, $conf);
1333 } elsif ($opt =~ m/^usb\d+$/) {
1334 if ($val =~ m/spice/) {
1335 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1336 } elsif ($authuser ne 'root@pam') {
1337 die "only root can delete '$opt' config for real devices\n";
1339 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1340 PVE
::QemuConfig-
>write_config($vmid, $conf);
1342 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1343 PVE
::QemuConfig-
>write_config($vmid, $conf);
1347 foreach my $opt (keys %$param) { # add/change
1348 $modified->{$opt} = 1;
1349 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1350 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1352 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1354 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1356 if ($conf->{$opt}) {
1357 $check_drive_perms->($opt, $conf->{$opt});
1361 $check_drive_perms->($opt, $param->{$opt});
1362 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1363 if defined($conf->{pending
}->{$opt});
1365 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1367 # default legacy boot order implies all cdroms anyway
1369 # append new CD drives to bootorder to mark them bootable
1370 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1371 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1372 push @bootorder, $opt;
1373 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1374 $modified->{boot
} = 1;
1377 } elsif ($opt =~ m/^serial\d+/) {
1378 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1379 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1380 } elsif ($authuser ne 'root@pam') {
1381 die "only root can modify '$opt' config for real devices\n";
1383 $conf->{pending
}->{$opt} = $param->{$opt};
1384 } elsif ($opt =~ m/^usb\d+/) {
1385 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1386 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1387 } elsif ($authuser ne 'root@pam') {
1388 die "only root can modify '$opt' config for real devices\n";
1390 $conf->{pending
}->{$opt} = $param->{$opt};
1392 $conf->{pending
}->{$opt} = $param->{$opt};
1394 if ($opt eq 'boot') {
1395 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1396 if ($new_bootcfg->{order
}) {
1397 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1398 for my $dev (@devs) {
1399 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1400 my $deleted = grep {$_ eq $dev} @delete;
1401 die "invalid bootorder: device '$dev' does not exist'\n"
1402 if !$exists || $deleted;
1405 # remove legacy boot order settings if new one set
1406 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1407 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1408 if $conf->{bootdisk
};
1412 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1413 PVE
::QemuConfig-
>write_config($vmid, $conf);
1416 # remove pending changes when nothing changed
1417 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1418 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1419 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1421 return if !scalar(keys %{$conf->{pending
}});
1423 my $running = PVE
::QemuServer
::check_running
($vmid);
1425 # apply pending changes
1427 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1431 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1433 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1435 raise_param_exc
($errors) if scalar(keys %$errors);
1444 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1446 if ($background_delay) {
1448 # Note: It would be better to do that in the Event based HTTPServer
1449 # to avoid blocking call to sleep.
1451 my $end_time = time() + $background_delay;
1453 my $task = PVE
::Tools
::upid_decode
($upid);
1456 while (time() < $end_time) {
1457 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1459 sleep(1); # this gets interrupted when child process ends
1463 my $status = PVE
::Tools
::upid_read_status
($upid);
1464 return if !PVE
::Tools
::upid_status_is_error
($status);
1473 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1476 my $vm_config_perm_list = [
1481 'VM.Config.Network',
1483 'VM.Config.Options',
1484 'VM.Config.Cloudinit',
1487 __PACKAGE__-
>register_method({
1488 name
=> 'update_vm_async',
1489 path
=> '{vmid}/config',
1493 description
=> "Set virtual machine options (asynchrounous API).",
1495 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1498 additionalProperties
=> 0,
1499 properties
=> PVE
::QemuServer
::json_config_properties
(
1501 node
=> get_standard_option
('pve-node'),
1502 vmid
=> get_standard_option
('pve-vmid'),
1503 skiplock
=> get_standard_option
('skiplock'),
1505 type
=> 'string', format
=> 'pve-configid-list',
1506 description
=> "A list of settings you want to delete.",
1510 type
=> 'string', format
=> 'pve-configid-list',
1511 description
=> "Revert a pending change.",
1516 description
=> $opt_force_description,
1518 requires
=> 'delete',
1522 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1526 background_delay
=> {
1528 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1539 code
=> $update_vm_api,
1542 __PACKAGE__-
>register_method({
1543 name
=> 'update_vm',
1544 path
=> '{vmid}/config',
1548 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1550 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1553 additionalProperties
=> 0,
1554 properties
=> PVE
::QemuServer
::json_config_properties
(
1556 node
=> get_standard_option
('pve-node'),
1557 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1558 skiplock
=> get_standard_option
('skiplock'),
1560 type
=> 'string', format
=> 'pve-configid-list',
1561 description
=> "A list of settings you want to delete.",
1565 type
=> 'string', format
=> 'pve-configid-list',
1566 description
=> "Revert a pending change.",
1571 description
=> $opt_force_description,
1573 requires
=> 'delete',
1577 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1583 returns
=> { type
=> 'null' },
1586 &$update_vm_api($param, 1);
1591 __PACKAGE__-
>register_method({
1592 name
=> 'destroy_vm',
1597 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1598 ." and firewall rules",
1600 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1603 additionalProperties
=> 0,
1605 node
=> get_standard_option
('pve-node'),
1606 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1607 skiplock
=> get_standard_option
('skiplock'),
1610 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1613 'destroy-unreferenced-disks' => {
1615 description
=> "If set, destroy additionally all disks not referenced in the config"
1616 ." but with a matching VMID from all enabled storages.",
1628 my $rpcenv = PVE
::RPCEnvironment
::get
();
1629 my $authuser = $rpcenv->get_user();
1630 my $vmid = $param->{vmid
};
1632 my $skiplock = $param->{skiplock
};
1633 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1634 if $skiplock && $authuser ne 'root@pam';
1636 my $early_checks = sub {
1638 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1639 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1641 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1643 if (!$param->{purge
}) {
1644 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1646 # don't allow destroy if with replication jobs but no purge param
1647 my $repl_conf = PVE
::ReplicationConfig-
>new();
1648 $repl_conf->check_for_existing_jobs($vmid);
1651 die "VM $vmid is running - destroy failed\n"
1652 if PVE
::QemuServer
::check_running
($vmid);
1662 my $storecfg = PVE
::Storage
::config
();
1664 syslog
('info', "destroy VM $vmid: $upid\n");
1665 PVE
::QemuConfig-
>lock_config($vmid, sub {
1666 # repeat, config might have changed
1667 my $ha_managed = $early_checks->();
1669 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1671 PVE
::QemuServer
::destroy_vm
(
1674 $skiplock, { lock => 'destroyed' },
1675 $purge_unreferenced,
1678 PVE
::AccessControl
::remove_vm_access
($vmid);
1679 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1680 if ($param->{purge
}) {
1681 print "purging VM $vmid from related configurations..\n";
1682 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1683 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1686 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1687 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1691 # only now remove the zombie config, else we can have reuse race
1692 PVE
::QemuConfig-
>destroy_config($vmid);
1696 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1699 __PACKAGE__-
>register_method({
1701 path
=> '{vmid}/unlink',
1705 description
=> "Unlink/delete disk images.",
1707 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1710 additionalProperties
=> 0,
1712 node
=> get_standard_option
('pve-node'),
1713 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1715 type
=> 'string', format
=> 'pve-configid-list',
1716 description
=> "A list of disk IDs you want to delete.",
1720 description
=> $opt_force_description,
1725 returns
=> { type
=> 'null'},
1729 $param->{delete} = extract_param
($param, 'idlist');
1731 __PACKAGE__-
>update_vm($param);
1736 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1737 my $gen_rand_chars = sub {
1740 die "invalid length $length" if $length < 1;
1742 my $min = ord('!'); # first printable ascii
1744 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1745 die "failed to generate random bytes!\n"
1748 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1755 __PACKAGE__-
>register_method({
1757 path
=> '{vmid}/vncproxy',
1761 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1763 description
=> "Creates a TCP VNC proxy connections.",
1765 additionalProperties
=> 0,
1767 node
=> get_standard_option
('pve-node'),
1768 vmid
=> get_standard_option
('pve-vmid'),
1772 description
=> "starts websockify instead of vncproxy",
1774 'generate-password' => {
1778 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1783 additionalProperties
=> 0,
1785 user
=> { type
=> 'string' },
1786 ticket
=> { type
=> 'string' },
1789 description
=> "Returned if requested with 'generate-password' param."
1790 ." Consists of printable ASCII characters ('!' .. '~').",
1793 cert
=> { type
=> 'string' },
1794 port
=> { type
=> 'integer' },
1795 upid
=> { type
=> 'string' },
1801 my $rpcenv = PVE
::RPCEnvironment
::get
();
1803 my $authuser = $rpcenv->get_user();
1805 my $vmid = $param->{vmid
};
1806 my $node = $param->{node
};
1807 my $websocket = $param->{websocket
};
1809 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1813 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1814 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1817 my $authpath = "/vms/$vmid";
1819 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1820 my $password = $ticket;
1821 if ($param->{'generate-password'}) {
1822 $password = $gen_rand_chars->(8);
1825 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1831 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1832 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1833 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1834 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1835 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1837 $family = PVE
::Tools
::get_host_address_family
($node);
1840 my $port = PVE
::Tools
::next_vnc_port
($family);
1847 syslog
('info', "starting vnc proxy $upid\n");
1851 if (defined($serial)) {
1853 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1855 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1856 '-timeout', $timeout, '-authpath', $authpath,
1857 '-perm', 'Sys.Console'];
1859 if ($param->{websocket
}) {
1860 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1861 push @$cmd, '-notls', '-listen', 'localhost';
1864 push @$cmd, '-c', @$remcmd, @$termcmd;
1866 PVE
::Tools
::run_command
($cmd);
1870 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1872 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1874 my $sock = IO
::Socket
::IP-
>new(
1879 GetAddrInfoFlags
=> 0,
1880 ) or die "failed to create socket: $!\n";
1881 # Inside the worker we shouldn't have any previous alarms
1882 # running anyway...:
1884 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1886 accept(my $cli, $sock) or die "connection failed: $!\n";
1889 if (PVE
::Tools
::run_command
($cmd,
1890 output
=> '>&'.fileno($cli),
1891 input
=> '<&'.fileno($cli),
1894 die "Failed to run vncproxy.\n";
1901 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1903 PVE
::Tools
::wait_for_vnc_port
($port);
1912 $res->{password
} = $password if $param->{'generate-password'};
1917 __PACKAGE__-
>register_method({
1918 name
=> 'termproxy',
1919 path
=> '{vmid}/termproxy',
1923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1925 description
=> "Creates a TCP proxy connections.",
1927 additionalProperties
=> 0,
1929 node
=> get_standard_option
('pve-node'),
1930 vmid
=> get_standard_option
('pve-vmid'),
1934 enum
=> [qw(serial0 serial1 serial2 serial3)],
1935 description
=> "opens a serial terminal (defaults to display)",
1940 additionalProperties
=> 0,
1942 user
=> { type
=> 'string' },
1943 ticket
=> { type
=> 'string' },
1944 port
=> { type
=> 'integer' },
1945 upid
=> { type
=> 'string' },
1951 my $rpcenv = PVE
::RPCEnvironment
::get
();
1953 my $authuser = $rpcenv->get_user();
1955 my $vmid = $param->{vmid
};
1956 my $node = $param->{node
};
1957 my $serial = $param->{serial
};
1959 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1961 if (!defined($serial)) {
1963 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1964 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1968 my $authpath = "/vms/$vmid";
1970 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1975 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1976 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1977 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1978 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1979 push @$remcmd, '--';
1981 $family = PVE
::Tools
::get_host_address_family
($node);
1984 my $port = PVE
::Tools
::next_vnc_port
($family);
1986 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1987 push @$termcmd, '-iface', $serial if $serial;
1992 syslog
('info', "starting qemu termproxy $upid\n");
1994 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1995 '--perm', 'VM.Console', '--'];
1996 push @$cmd, @$remcmd, @$termcmd;
1998 PVE
::Tools
::run_command
($cmd);
2001 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2003 PVE
::Tools
::wait_for_vnc_port
($port);
2013 __PACKAGE__-
>register_method({
2014 name
=> 'vncwebsocket',
2015 path
=> '{vmid}/vncwebsocket',
2018 description
=> "You also need to pass a valid ticket (vncticket).",
2019 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2021 description
=> "Opens a weksocket for VNC traffic.",
2023 additionalProperties
=> 0,
2025 node
=> get_standard_option
('pve-node'),
2026 vmid
=> get_standard_option
('pve-vmid'),
2028 description
=> "Ticket from previous call to vncproxy.",
2033 description
=> "Port number returned by previous vncproxy call.",
2043 port
=> { type
=> 'string' },
2049 my $rpcenv = PVE
::RPCEnvironment
::get
();
2051 my $authuser = $rpcenv->get_user();
2053 my $vmid = $param->{vmid
};
2054 my $node = $param->{node
};
2056 my $authpath = "/vms/$vmid";
2058 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2060 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2062 # Note: VNC ports are acessible from outside, so we do not gain any
2063 # security if we verify that $param->{port} belongs to VM $vmid. This
2064 # check is done by verifying the VNC ticket (inside VNC protocol).
2066 my $port = $param->{port
};
2068 return { port
=> $port };
2071 __PACKAGE__-
>register_method({
2072 name
=> 'spiceproxy',
2073 path
=> '{vmid}/spiceproxy',
2078 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2080 description
=> "Returns a SPICE configuration to connect to the VM.",
2082 additionalProperties
=> 0,
2084 node
=> get_standard_option
('pve-node'),
2085 vmid
=> get_standard_option
('pve-vmid'),
2086 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2089 returns
=> get_standard_option
('remote-viewer-config'),
2093 my $rpcenv = PVE
::RPCEnvironment
::get
();
2095 my $authuser = $rpcenv->get_user();
2097 my $vmid = $param->{vmid
};
2098 my $node = $param->{node
};
2099 my $proxy = $param->{proxy
};
2101 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2102 my $title = "VM $vmid";
2103 $title .= " - ". $conf->{name
} if $conf->{name
};
2105 my $port = PVE
::QemuServer
::spice_port
($vmid);
2107 my ($ticket, undef, $remote_viewer_config) =
2108 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2110 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2111 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2113 return $remote_viewer_config;
2116 __PACKAGE__-
>register_method({
2118 path
=> '{vmid}/status',
2121 description
=> "Directory index",
2126 additionalProperties
=> 0,
2128 node
=> get_standard_option
('pve-node'),
2129 vmid
=> get_standard_option
('pve-vmid'),
2137 subdir
=> { type
=> 'string' },
2140 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2146 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2149 { subdir
=> 'current' },
2150 { subdir
=> 'start' },
2151 { subdir
=> 'stop' },
2152 { subdir
=> 'reset' },
2153 { subdir
=> 'shutdown' },
2154 { subdir
=> 'suspend' },
2155 { subdir
=> 'reboot' },
2161 __PACKAGE__-
>register_method({
2162 name
=> 'vm_status',
2163 path
=> '{vmid}/status/current',
2166 protected
=> 1, # qemu pid files are only readable by root
2167 description
=> "Get virtual machine status.",
2169 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2172 additionalProperties
=> 0,
2174 node
=> get_standard_option
('pve-node'),
2175 vmid
=> get_standard_option
('pve-vmid'),
2181 %$PVE::QemuServer
::vmstatus_return_properties
,
2183 description
=> "HA manager service status.",
2187 description
=> "Qemu VGA configuration supports spice.",
2192 description
=> "Qemu GuestAgent enabled in config.",
2202 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2204 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2205 my $status = $vmstatus->{$param->{vmid
}};
2207 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2209 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2210 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2215 __PACKAGE__-
>register_method({
2217 path
=> '{vmid}/status/start',
2221 description
=> "Start virtual machine.",
2223 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2226 additionalProperties
=> 0,
2228 node
=> get_standard_option
('pve-node'),
2229 vmid
=> get_standard_option
('pve-vmid',
2230 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2231 skiplock
=> get_standard_option
('skiplock'),
2232 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2233 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2236 enum
=> ['secure', 'insecure'],
2237 description
=> "Migration traffic is encrypted using an SSH " .
2238 "tunnel by default. On secure, completely private networks " .
2239 "this can be disabled to increase performance.",
2242 migration_network
=> {
2243 type
=> 'string', format
=> 'CIDR',
2244 description
=> "CIDR of the (sub) network that is used for migration.",
2247 machine
=> get_standard_option
('pve-qemu-machine'),
2249 description
=> "Override QEMU's -cpu argument with the given string.",
2253 targetstorage
=> get_standard_option
('pve-targetstorage'),
2255 description
=> "Wait maximal timeout seconds.",
2258 default => 'max(30, vm memory in GiB)',
2269 my $rpcenv = PVE
::RPCEnvironment
::get
();
2270 my $authuser = $rpcenv->get_user();
2272 my $node = extract_param
($param, 'node');
2273 my $vmid = extract_param
($param, 'vmid');
2274 my $timeout = extract_param
($param, 'timeout');
2276 my $machine = extract_param
($param, 'machine');
2277 my $force_cpu = extract_param
($param, 'force-cpu');
2279 my $get_root_param = sub {
2280 my $value = extract_param
($param, $_[0]);
2281 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2282 if $value && $authuser ne 'root@pam';
2286 my $stateuri = $get_root_param->('stateuri');
2287 my $skiplock = $get_root_param->('skiplock');
2288 my $migratedfrom = $get_root_param->('migratedfrom');
2289 my $migration_type = $get_root_param->('migration_type');
2290 my $migration_network = $get_root_param->('migration_network');
2291 my $targetstorage = $get_root_param->('targetstorage');
2295 if ($targetstorage) {
2296 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2298 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2299 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2303 # read spice ticket from STDIN
2305 my $nbd_protocol_version = 0;
2306 my $replicated_volumes = {};
2307 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2308 while (defined(my $line = <STDIN
>)) {
2310 if ($line =~ m/^spice_ticket: (.+)$/) {
2312 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2313 $nbd_protocol_version = $1;
2314 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2315 $replicated_volumes->{$1} = 1;
2317 # fallback for old source node
2318 $spice_ticket = $line;
2323 PVE
::Cluster
::check_cfs_quorum
();
2325 my $storecfg = PVE
::Storage
::config
();
2327 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2331 print "Requesting HA start for VM $vmid\n";
2333 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2334 PVE
::Tools
::run_command
($cmd);
2338 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2345 syslog
('info', "start VM $vmid: $upid\n");
2347 my $migrate_opts = {
2348 migratedfrom
=> $migratedfrom,
2349 spice_ticket
=> $spice_ticket,
2350 network
=> $migration_network,
2351 type
=> $migration_type,
2352 storagemap
=> $storagemap,
2353 nbd_proto_version
=> $nbd_protocol_version,
2354 replicated_volumes
=> $replicated_volumes,
2358 statefile
=> $stateuri,
2359 skiplock
=> $skiplock,
2360 forcemachine
=> $machine,
2361 timeout
=> $timeout,
2362 forcecpu
=> $force_cpu,
2365 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2369 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2373 __PACKAGE__-
>register_method({
2375 path
=> '{vmid}/status/stop',
2379 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2380 "is akin to pulling the power plug of a running computer and may damage the VM data",
2382 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2385 additionalProperties
=> 0,
2387 node
=> get_standard_option
('pve-node'),
2388 vmid
=> get_standard_option
('pve-vmid',
2389 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2390 skiplock
=> get_standard_option
('skiplock'),
2391 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2393 description
=> "Wait maximal timeout seconds.",
2399 description
=> "Do not deactivate storage volumes.",
2412 my $rpcenv = PVE
::RPCEnvironment
::get
();
2413 my $authuser = $rpcenv->get_user();
2415 my $node = extract_param
($param, 'node');
2416 my $vmid = extract_param
($param, 'vmid');
2418 my $skiplock = extract_param
($param, 'skiplock');
2419 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2420 if $skiplock && $authuser ne 'root@pam';
2422 my $keepActive = extract_param
($param, 'keepActive');
2423 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2424 if $keepActive && $authuser ne 'root@pam';
2426 my $migratedfrom = extract_param
($param, 'migratedfrom');
2427 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2428 if $migratedfrom && $authuser ne 'root@pam';
2431 my $storecfg = PVE
::Storage
::config
();
2433 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2438 print "Requesting HA stop for VM $vmid\n";
2440 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2441 PVE
::Tools
::run_command
($cmd);
2445 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2451 syslog
('info', "stop VM $vmid: $upid\n");
2453 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2454 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2458 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2462 __PACKAGE__-
>register_method({
2464 path
=> '{vmid}/status/reset',
2468 description
=> "Reset virtual machine.",
2470 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2473 additionalProperties
=> 0,
2475 node
=> get_standard_option
('pve-node'),
2476 vmid
=> get_standard_option
('pve-vmid',
2477 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2478 skiplock
=> get_standard_option
('skiplock'),
2487 my $rpcenv = PVE
::RPCEnvironment
::get
();
2489 my $authuser = $rpcenv->get_user();
2491 my $node = extract_param
($param, 'node');
2493 my $vmid = extract_param
($param, 'vmid');
2495 my $skiplock = extract_param
($param, 'skiplock');
2496 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2497 if $skiplock && $authuser ne 'root@pam';
2499 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2504 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2509 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2512 __PACKAGE__-
>register_method({
2513 name
=> 'vm_shutdown',
2514 path
=> '{vmid}/status/shutdown',
2518 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2519 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2521 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2524 additionalProperties
=> 0,
2526 node
=> get_standard_option
('pve-node'),
2527 vmid
=> get_standard_option
('pve-vmid',
2528 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2529 skiplock
=> get_standard_option
('skiplock'),
2531 description
=> "Wait maximal timeout seconds.",
2537 description
=> "Make sure the VM stops.",
2543 description
=> "Do not deactivate storage volumes.",
2556 my $rpcenv = PVE
::RPCEnvironment
::get
();
2557 my $authuser = $rpcenv->get_user();
2559 my $node = extract_param
($param, 'node');
2560 my $vmid = extract_param
($param, 'vmid');
2562 my $skiplock = extract_param
($param, 'skiplock');
2563 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2564 if $skiplock && $authuser ne 'root@pam';
2566 my $keepActive = extract_param
($param, 'keepActive');
2567 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2568 if $keepActive && $authuser ne 'root@pam';
2570 my $storecfg = PVE
::Storage
::config
();
2574 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2575 # otherwise, we will infer a shutdown command, but run into the timeout,
2576 # then when the vm is resumed, it will instantly shutdown
2578 # checking the qmp status here to get feedback to the gui/cli/api
2579 # and the status query should not take too long
2580 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2581 if ($param->{forceStop
}) {
2582 warn "VM is paused - stop instead of shutdown\n";
2585 die "VM is paused - cannot shutdown\n";
2589 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2591 my $timeout = $param->{timeout
} // 60;
2595 print "Requesting HA stop for VM $vmid\n";
2597 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2598 PVE
::Tools
::run_command
($cmd);
2602 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2609 syslog
('info', "shutdown VM $vmid: $upid\n");
2611 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2612 $shutdown, $param->{forceStop
}, $keepActive);
2616 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2620 __PACKAGE__-
>register_method({
2621 name
=> 'vm_reboot',
2622 path
=> '{vmid}/status/reboot',
2626 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2628 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2631 additionalProperties
=> 0,
2633 node
=> get_standard_option
('pve-node'),
2634 vmid
=> get_standard_option
('pve-vmid',
2635 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2637 description
=> "Wait maximal timeout seconds for the shutdown.",
2650 my $rpcenv = PVE
::RPCEnvironment
::get
();
2651 my $authuser = $rpcenv->get_user();
2653 my $node = extract_param
($param, 'node');
2654 my $vmid = extract_param
($param, 'vmid');
2656 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2658 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2663 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2664 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2668 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2671 __PACKAGE__-
>register_method({
2672 name
=> 'vm_suspend',
2673 path
=> '{vmid}/status/suspend',
2677 description
=> "Suspend virtual machine.",
2679 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2680 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2681 " on the storage for the vmstate.",
2682 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2685 additionalProperties
=> 0,
2687 node
=> get_standard_option
('pve-node'),
2688 vmid
=> get_standard_option
('pve-vmid',
2689 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2690 skiplock
=> get_standard_option
('skiplock'),
2695 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2697 statestorage
=> get_standard_option
('pve-storage-id', {
2698 description
=> "The storage for the VM state",
2699 requires
=> 'todisk',
2701 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2711 my $rpcenv = PVE
::RPCEnvironment
::get
();
2712 my $authuser = $rpcenv->get_user();
2714 my $node = extract_param
($param, 'node');
2715 my $vmid = extract_param
($param, 'vmid');
2717 my $todisk = extract_param
($param, 'todisk') // 0;
2719 my $statestorage = extract_param
($param, 'statestorage');
2721 my $skiplock = extract_param
($param, 'skiplock');
2722 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2723 if $skiplock && $authuser ne 'root@pam';
2725 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2727 die "Cannot suspend HA managed VM to disk\n"
2728 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2730 # early check for storage permission, for better user feedback
2732 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2734 if (!$statestorage) {
2735 # get statestorage from config if none is given
2736 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2737 my $storecfg = PVE
::Storage
::config
();
2738 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2741 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2747 syslog
('info', "suspend VM $vmid: $upid\n");
2749 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2754 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2755 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2758 __PACKAGE__-
>register_method({
2759 name
=> 'vm_resume',
2760 path
=> '{vmid}/status/resume',
2764 description
=> "Resume virtual machine.",
2766 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2769 additionalProperties
=> 0,
2771 node
=> get_standard_option
('pve-node'),
2772 vmid
=> get_standard_option
('pve-vmid',
2773 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2774 skiplock
=> get_standard_option
('skiplock'),
2775 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2785 my $rpcenv = PVE
::RPCEnvironment
::get
();
2787 my $authuser = $rpcenv->get_user();
2789 my $node = extract_param
($param, 'node');
2791 my $vmid = extract_param
($param, 'vmid');
2793 my $skiplock = extract_param
($param, 'skiplock');
2794 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2795 if $skiplock && $authuser ne 'root@pam';
2797 my $nocheck = extract_param
($param, 'nocheck');
2798 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2799 if $nocheck && $authuser ne 'root@pam';
2801 my $to_disk_suspended;
2803 PVE
::QemuConfig-
>lock_config($vmid, sub {
2804 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2805 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2809 die "VM $vmid not running\n"
2810 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2815 syslog
('info', "resume VM $vmid: $upid\n");
2817 if (!$to_disk_suspended) {
2818 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2820 my $storecfg = PVE
::Storage
::config
();
2821 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2827 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2830 __PACKAGE__-
>register_method({
2831 name
=> 'vm_sendkey',
2832 path
=> '{vmid}/sendkey',
2836 description
=> "Send key event to virtual machine.",
2838 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2841 additionalProperties
=> 0,
2843 node
=> get_standard_option
('pve-node'),
2844 vmid
=> get_standard_option
('pve-vmid',
2845 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2846 skiplock
=> get_standard_option
('skiplock'),
2848 description
=> "The key (qemu monitor encoding).",
2853 returns
=> { type
=> 'null'},
2857 my $rpcenv = PVE
::RPCEnvironment
::get
();
2859 my $authuser = $rpcenv->get_user();
2861 my $node = extract_param
($param, 'node');
2863 my $vmid = extract_param
($param, 'vmid');
2865 my $skiplock = extract_param
($param, 'skiplock');
2866 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2867 if $skiplock && $authuser ne 'root@pam';
2869 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2874 __PACKAGE__-
>register_method({
2875 name
=> 'vm_feature',
2876 path
=> '{vmid}/feature',
2880 description
=> "Check if feature for virtual machine is available.",
2882 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2885 additionalProperties
=> 0,
2887 node
=> get_standard_option
('pve-node'),
2888 vmid
=> get_standard_option
('pve-vmid'),
2890 description
=> "Feature to check.",
2892 enum
=> [ 'snapshot', 'clone', 'copy' ],
2894 snapname
=> get_standard_option
('pve-snapshot-name', {
2902 hasFeature
=> { type
=> 'boolean' },
2905 items
=> { type
=> 'string' },
2912 my $node = extract_param
($param, 'node');
2914 my $vmid = extract_param
($param, 'vmid');
2916 my $snapname = extract_param
($param, 'snapname');
2918 my $feature = extract_param
($param, 'feature');
2920 my $running = PVE
::QemuServer
::check_running
($vmid);
2922 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2925 my $snap = $conf->{snapshots
}->{$snapname};
2926 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2929 my $storecfg = PVE
::Storage
::config
();
2931 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2932 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2935 hasFeature
=> $hasFeature,
2936 nodes
=> [ keys %$nodelist ],
2940 __PACKAGE__-
>register_method({
2942 path
=> '{vmid}/clone',
2946 description
=> "Create a copy of virtual machine/template.",
2948 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2949 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2950 "'Datastore.AllocateSpace' on any used storage.",
2953 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2955 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2956 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2961 additionalProperties
=> 0,
2963 node
=> get_standard_option
('pve-node'),
2964 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2965 newid
=> get_standard_option
('pve-vmid', {
2966 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2967 description
=> 'VMID for the clone.' }),
2970 type
=> 'string', format
=> 'dns-name',
2971 description
=> "Set a name for the new VM.",
2976 description
=> "Description for the new VM.",
2980 type
=> 'string', format
=> 'pve-poolid',
2981 description
=> "Add the new VM to the specified pool.",
2983 snapname
=> get_standard_option
('pve-snapshot-name', {
2986 storage
=> get_standard_option
('pve-storage-id', {
2987 description
=> "Target storage for full clone.",
2991 description
=> "Target format for file storage. Only valid for full clone.",
2994 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2999 description
=> "Create a full copy of all disks. This is always done when " .
3000 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3002 target
=> get_standard_option
('pve-node', {
3003 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3007 description
=> "Override I/O bandwidth limit (in KiB/s).",
3011 default => 'clone limit from datacenter or storage config',
3021 my $rpcenv = PVE
::RPCEnvironment
::get
();
3022 my $authuser = $rpcenv->get_user();
3024 my $node = extract_param
($param, 'node');
3025 my $vmid = extract_param
($param, 'vmid');
3026 my $newid = extract_param
($param, 'newid');
3027 my $pool = extract_param
($param, 'pool');
3028 $rpcenv->check_pool_exist($pool) if defined($pool);
3030 my $snapname = extract_param
($param, 'snapname');
3031 my $storage = extract_param
($param, 'storage');
3032 my $format = extract_param
($param, 'format');
3033 my $target = extract_param
($param, 'target');
3035 my $localnode = PVE
::INotify
::nodename
();
3037 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3041 PVE
::Cluster
::check_node_exists
($target) if $target;
3043 my $storecfg = PVE
::Storage
::config
();
3046 # check if storage is enabled on local node
3047 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3049 # check if storage is available on target node
3050 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3051 # clone only works if target storage is shared
3052 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3053 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3057 PVE
::Cluster
::check_cfs_quorum
();
3059 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3062 # do all tests after lock but before forking worker - if possible
3064 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3065 PVE
::QemuConfig-
>check_lock($conf);
3067 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3068 die "unexpected state change\n" if $verify_running != $running;
3070 die "snapshot '$snapname' does not exist\n"
3071 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3073 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3075 die "parameter 'storage' not allowed for linked clones\n"
3076 if defined($storage) && !$full;
3078 die "parameter 'format' not allowed for linked clones\n"
3079 if defined($format) && !$full;
3081 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3083 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3085 die "can't clone VM to node '$target' (VM uses local storage)\n"
3086 if $target && !$sharedvm;
3088 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3089 die "unable to create VM $newid: config file already exists\n"
3092 my $newconf = { lock => 'clone' };
3097 foreach my $opt (keys %$oldconf) {
3098 my $value = $oldconf->{$opt};
3100 # do not copy snapshot related info
3101 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3102 $opt eq 'vmstate' || $opt eq 'snapstate';
3104 # no need to copy unused images, because VMID(owner) changes anyways
3105 next if $opt =~ m/^unused\d+$/;
3107 # always change MAC! address
3108 if ($opt =~ m/^net(\d+)$/) {
3109 my $net = PVE
::QemuServer
::parse_net
($value);
3110 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3111 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3112 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3113 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3114 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3115 die "unable to parse drive options for '$opt'\n" if !$drive;
3116 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3117 $newconf->{$opt} = $value; # simply copy configuration
3119 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3120 die "Full clone feature is not supported for drive '$opt'\n"
3121 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3122 $fullclone->{$opt} = 1;
3124 # not full means clone instead of copy
3125 die "Linked clone feature is not supported for drive '$opt'\n"
3126 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3128 $drives->{$opt} = $drive;
3129 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3130 push @$vollist, $drive->{file
};
3133 # copy everything else
3134 $newconf->{$opt} = $value;
3138 # auto generate a new uuid
3139 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3140 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3141 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3142 # auto generate a new vmgenid only if the option was set for template
3143 if ($newconf->{vmgenid
}) {
3144 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3147 delete $newconf->{template
};
3149 if ($param->{name
}) {
3150 $newconf->{name
} = $param->{name
};
3152 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3155 if ($param->{description
}) {
3156 $newconf->{description
} = $param->{description
};
3159 # create empty/temp config - this fails if VM already exists on other node
3160 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3161 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3166 my $newvollist = [];
3173 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3175 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3177 my $bwlimit = extract_param
($param, 'bwlimit');
3179 my $total_jobs = scalar(keys %{$drives});
3182 foreach my $opt (sort keys %$drives) {
3183 my $drive = $drives->{$opt};
3184 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3185 my $completion = $skipcomplete ?
'skip' : 'complete';
3187 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3188 my $storage_list = [ $src_sid ];
3189 push @$storage_list, $storage if defined($storage);
3190 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3192 my $newdrive = PVE
::QemuServer
::clone_disk
(
3211 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3213 PVE
::QemuConfig-
>write_config($newid, $newconf);
3217 delete $newconf->{lock};
3219 # do not write pending changes
3220 if (my @changes = keys %{$newconf->{pending
}}) {
3221 my $pending = join(',', @changes);
3222 warn "found pending changes for '$pending', discarding for clone\n";
3223 delete $newconf->{pending
};
3226 PVE
::QemuConfig-
>write_config($newid, $newconf);
3229 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3230 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3231 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3233 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3234 die "Failed to move config to node '$target' - rename failed: $!\n"
3235 if !rename($conffile, $newconffile);
3238 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3241 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3242 sleep 1; # some storage like rbd need to wait before release volume - really?
3244 foreach my $volid (@$newvollist) {
3245 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3249 PVE
::Firewall
::remove_vmfw_conf
($newid);
3251 unlink $conffile; # avoid races -> last thing before die
3253 die "clone failed: $err";
3259 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3261 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3264 # Aquire exclusive lock lock for $newid
3265 my $lock_target_vm = sub {
3266 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3269 # exclusive lock if VM is running - else shared lock is enough;
3271 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3273 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3277 __PACKAGE__-
>register_method({
3278 name
=> 'move_vm_disk',
3279 path
=> '{vmid}/move_disk',
3283 description
=> "Move volume to different storage.",
3285 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3287 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3288 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3292 additionalProperties
=> 0,
3294 node
=> get_standard_option
('pve-node'),
3295 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3298 description
=> "The disk you want to move.",
3299 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3301 storage
=> get_standard_option
('pve-storage-id', {
3302 description
=> "Target storage.",
3303 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3307 description
=> "Target Format.",
3308 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3313 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3319 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3324 description
=> "Override I/O bandwidth limit (in KiB/s).",
3328 default => 'move limit from datacenter or storage config',
3334 description
=> "the task ID.",
3339 my $rpcenv = PVE
::RPCEnvironment
::get
();
3340 my $authuser = $rpcenv->get_user();
3342 my $node = extract_param
($param, 'node');
3343 my $vmid = extract_param
($param, 'vmid');
3344 my $digest = extract_param
($param, 'digest');
3345 my $disk = extract_param
($param, 'disk');
3346 my $storeid = extract_param
($param, 'storage');
3347 my $format = extract_param
($param, 'format');
3349 my $storecfg = PVE
::Storage
::config
();
3351 my $updatefn = sub {
3352 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3353 PVE
::QemuConfig-
>check_lock($conf);
3355 die "VM config checksum missmatch (file change by other user?)\n"
3356 if $digest && $digest ne $conf->{digest
};
3358 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3360 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3362 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3363 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3365 my $old_volid = $drive->{file
};
3367 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3368 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3372 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3373 (!$format || !$oldfmt || $oldfmt eq $format);
3375 # this only checks snapshots because $disk is passed!
3376 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3377 die "you can't move a disk with snapshots and delete the source\n"
3378 if $snapshotted && $param->{delete};
3380 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3382 my $running = PVE
::QemuServer
::check_running
($vmid);
3384 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3387 my $newvollist = [];
3393 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3395 warn "moving disk with snapshots, snapshots will not be moved!\n"
3398 my $bwlimit = extract_param
($param, 'bwlimit');
3399 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3401 my $newdrive = PVE
::QemuServer
::clone_disk
(
3419 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3421 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3423 # convert moved disk to base if part of template
3424 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3425 if PVE
::QemuConfig-
>is_template($conf);
3427 PVE
::QemuConfig-
>write_config($vmid, $conf);
3429 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3430 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3431 eval { mon_cmd
($vmid, "guest-fstrim") };
3435 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3436 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3442 foreach my $volid (@$newvollist) {
3443 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3446 die "storage migration failed: $err";
3449 if ($param->{delete}) {
3451 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3452 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3458 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3461 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3464 my $check_vm_disks_local = sub {
3465 my ($storecfg, $vmconf, $vmid) = @_;
3467 my $local_disks = {};
3469 # add some more information to the disks e.g. cdrom
3470 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3471 my ($volid, $attr) = @_;
3473 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3475 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3476 return if $scfg->{shared
};
3478 # The shared attr here is just a special case where the vdisk
3479 # is marked as shared manually
3480 return if $attr->{shared
};
3481 return if $attr->{cdrom
} and $volid eq "none";
3483 if (exists $local_disks->{$volid}) {
3484 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3486 $local_disks->{$volid} = $attr;
3487 # ensure volid is present in case it's needed
3488 $local_disks->{$volid}->{volid
} = $volid;
3492 return $local_disks;
3495 __PACKAGE__-
>register_method({
3496 name
=> 'migrate_vm_precondition',
3497 path
=> '{vmid}/migrate',
3501 description
=> "Get preconditions for migration.",
3503 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3506 additionalProperties
=> 0,
3508 node
=> get_standard_option
('pve-node'),
3509 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3510 target
=> get_standard_option
('pve-node', {
3511 description
=> "Target node.",
3512 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3520 running
=> { type
=> 'boolean' },
3524 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3526 not_allowed_nodes
=> {
3529 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3533 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3535 local_resources
=> {
3537 description
=> "List local resources e.g. pci, usb"
3544 my $rpcenv = PVE
::RPCEnvironment
::get
();
3546 my $authuser = $rpcenv->get_user();
3548 PVE
::Cluster
::check_cfs_quorum
();
3552 my $vmid = extract_param
($param, 'vmid');
3553 my $target = extract_param
($param, 'target');
3554 my $localnode = PVE
::INotify
::nodename
();
3558 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3559 my $storecfg = PVE
::Storage
::config
();
3562 # try to detect errors early
3563 PVE
::QemuConfig-
>check_lock($vmconf);
3565 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3567 # if vm is not running, return target nodes where local storage is available
3568 # for offline migration
3569 if (!$res->{running
}) {
3570 $res->{allowed_nodes
} = [];
3571 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3572 delete $checked_nodes->{$localnode};
3574 foreach my $node (keys %$checked_nodes) {
3575 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3576 push @{$res->{allowed_nodes
}}, $node;
3580 $res->{not_allowed_nodes
} = $checked_nodes;
3584 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3585 $res->{local_disks
} = [ values %$local_disks ];;
3587 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3589 $res->{local_resources
} = $local_resources;
3596 __PACKAGE__-
>register_method({
3597 name
=> 'migrate_vm',
3598 path
=> '{vmid}/migrate',
3602 description
=> "Migrate virtual machine. Creates a new migration task.",
3604 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3607 additionalProperties
=> 0,
3609 node
=> get_standard_option
('pve-node'),
3610 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3611 target
=> get_standard_option
('pve-node', {
3612 description
=> "Target node.",
3613 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3617 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3622 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3627 enum
=> ['secure', 'insecure'],
3628 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3631 migration_network
=> {
3632 type
=> 'string', format
=> 'CIDR',
3633 description
=> "CIDR of the (sub) network that is used for migration.",
3636 "with-local-disks" => {
3638 description
=> "Enable live storage migration for local disk",
3641 targetstorage
=> get_standard_option
('pve-targetstorage', {
3642 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3645 description
=> "Override I/O bandwidth limit (in KiB/s).",
3649 default => 'migrate limit from datacenter or storage config',
3655 description
=> "the task ID.",
3660 my $rpcenv = PVE
::RPCEnvironment
::get
();
3661 my $authuser = $rpcenv->get_user();
3663 my $target = extract_param
($param, 'target');
3665 my $localnode = PVE
::INotify
::nodename
();
3666 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3668 PVE
::Cluster
::check_cfs_quorum
();
3670 PVE
::Cluster
::check_node_exists
($target);
3672 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3674 my $vmid = extract_param
($param, 'vmid');
3676 raise_param_exc
({ force
=> "Only root may use this option." })
3677 if $param->{force
} && $authuser ne 'root@pam';
3679 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3680 if $param->{migration_type
} && $authuser ne 'root@pam';
3682 # allow root only until better network permissions are available
3683 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3684 if $param->{migration_network
} && $authuser ne 'root@pam';
3687 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3689 # try to detect errors early
3691 PVE
::QemuConfig-
>check_lock($conf);
3693 if (PVE
::QemuServer
::check_running
($vmid)) {
3694 die "can't migrate running VM without --online\n" if !$param->{online
};
3696 my $repl_conf = PVE
::ReplicationConfig-
>new();
3697 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3698 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3699 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3700 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3701 "target. Use 'force' to override.\n";
3704 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3705 $param->{online
} = 0;
3708 my $storecfg = PVE
::Storage
::config
();
3710 if (my $targetstorage = $param->{targetstorage
}) {
3711 my $check_storage = sub {
3712 my ($target_sid) = @_;
3713 PVE
::Storage
::storage_check_enabled
($storecfg, $target_sid, $target);
3714 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3715 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3716 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3717 if !$scfg->{content
}->{images
};
3720 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3721 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3724 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3725 if !defined($storagemap->{identity
});
3727 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3728 $check_storage->($target_sid);
3731 $check_storage->($storagemap->{default})
3732 if $storagemap->{default};
3734 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3735 if $storagemap->{identity
};
3737 $param->{storagemap
} = $storagemap;
3739 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3742 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3747 print "Requesting HA migration for VM $vmid to node $target\n";
3749 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3750 PVE
::Tools
::run_command
($cmd);
3754 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3759 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3763 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3766 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3771 __PACKAGE__-
>register_method({
3773 path
=> '{vmid}/monitor',
3777 description
=> "Execute Qemu monitor commands.",
3779 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3780 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3783 additionalProperties
=> 0,
3785 node
=> get_standard_option
('pve-node'),
3786 vmid
=> get_standard_option
('pve-vmid'),
3789 description
=> "The monitor command.",
3793 returns
=> { type
=> 'string'},
3797 my $rpcenv = PVE
::RPCEnvironment
::get
();
3798 my $authuser = $rpcenv->get_user();
3801 my $command = shift;
3802 return $command =~ m/^\s*info(\s+|$)/
3803 || $command =~ m/^\s*help\s*$/;
3806 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3807 if !&$is_ro($param->{command
});
3809 my $vmid = $param->{vmid
};
3811 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3815 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3817 $res = "ERROR: $@" if $@;
3822 __PACKAGE__-
>register_method({
3823 name
=> 'resize_vm',
3824 path
=> '{vmid}/resize',
3828 description
=> "Extend volume size.",
3830 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3833 additionalProperties
=> 0,
3835 node
=> get_standard_option
('pve-node'),
3836 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3837 skiplock
=> get_standard_option
('skiplock'),
3840 description
=> "The disk you want to resize.",
3841 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3845 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3846 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.",
3850 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3856 returns
=> { type
=> 'null'},
3860 my $rpcenv = PVE
::RPCEnvironment
::get
();
3862 my $authuser = $rpcenv->get_user();
3864 my $node = extract_param
($param, 'node');
3866 my $vmid = extract_param
($param, 'vmid');
3868 my $digest = extract_param
($param, 'digest');
3870 my $disk = extract_param
($param, 'disk');
3872 my $sizestr = extract_param
($param, 'size');
3874 my $skiplock = extract_param
($param, 'skiplock');
3875 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3876 if $skiplock && $authuser ne 'root@pam';
3878 my $storecfg = PVE
::Storage
::config
();
3880 my $updatefn = sub {
3882 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3884 die "checksum missmatch (file change by other user?)\n"
3885 if $digest && $digest ne $conf->{digest
};
3886 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3888 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3890 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3892 my (undef, undef, undef, undef, undef, undef, $format) =
3893 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3895 die "can't resize volume: $disk if snapshot exists\n"
3896 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3898 my $volid = $drive->{file
};
3900 die "disk '$disk' has no associated volume\n" if !$volid;
3902 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3904 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3906 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3908 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3909 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3911 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3913 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3914 my ($ext, $newsize, $unit) = ($1, $2, $4);
3917 $newsize = $newsize * 1024;
3918 } elsif ($unit eq 'M') {
3919 $newsize = $newsize * 1024 * 1024;
3920 } elsif ($unit eq 'G') {
3921 $newsize = $newsize * 1024 * 1024 * 1024;
3922 } elsif ($unit eq 'T') {
3923 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3926 $newsize += $size if $ext;
3927 $newsize = int($newsize);
3929 die "shrinking disks is not supported\n" if $newsize < $size;
3931 return if $size == $newsize;
3933 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3935 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3937 $drive->{size
} = $newsize;
3938 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3940 PVE
::QemuConfig-
>write_config($vmid, $conf);
3943 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3947 __PACKAGE__-
>register_method({
3948 name
=> 'snapshot_list',
3949 path
=> '{vmid}/snapshot',
3951 description
=> "List all snapshots.",
3953 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3956 protected
=> 1, # qemu pid files are only readable by root
3958 additionalProperties
=> 0,
3960 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3961 node
=> get_standard_option
('pve-node'),
3970 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3974 description
=> "Snapshot includes RAM.",
3979 description
=> "Snapshot description.",
3983 description
=> "Snapshot creation time",
3985 renderer
=> 'timestamp',
3989 description
=> "Parent snapshot identifier.",
3995 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4000 my $vmid = $param->{vmid
};
4002 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4003 my $snaphash = $conf->{snapshots
} || {};
4007 foreach my $name (keys %$snaphash) {
4008 my $d = $snaphash->{$name};
4011 snaptime
=> $d->{snaptime
} || 0,
4012 vmstate
=> $d->{vmstate
} ?
1 : 0,
4013 description
=> $d->{description
} || '',
4015 $item->{parent
} = $d->{parent
} if $d->{parent
};
4016 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4020 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4023 digest
=> $conf->{digest
},
4024 running
=> $running,
4025 description
=> "You are here!",
4027 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4029 push @$res, $current;
4034 __PACKAGE__-
>register_method({
4036 path
=> '{vmid}/snapshot',
4040 description
=> "Snapshot a VM.",
4042 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4045 additionalProperties
=> 0,
4047 node
=> get_standard_option
('pve-node'),
4048 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4049 snapname
=> get_standard_option
('pve-snapshot-name'),
4053 description
=> "Save the vmstate",
4058 description
=> "A textual description or comment.",
4064 description
=> "the task ID.",
4069 my $rpcenv = PVE
::RPCEnvironment
::get
();
4071 my $authuser = $rpcenv->get_user();
4073 my $node = extract_param
($param, 'node');
4075 my $vmid = extract_param
($param, 'vmid');
4077 my $snapname = extract_param
($param, 'snapname');
4079 die "unable to use snapshot name 'current' (reserved name)\n"
4080 if $snapname eq 'current';
4082 die "unable to use snapshot name 'pending' (reserved name)\n"
4083 if lc($snapname) eq 'pending';
4086 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4087 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4088 $param->{description
});
4091 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4094 __PACKAGE__-
>register_method({
4095 name
=> 'snapshot_cmd_idx',
4096 path
=> '{vmid}/snapshot/{snapname}',
4103 additionalProperties
=> 0,
4105 vmid
=> get_standard_option
('pve-vmid'),
4106 node
=> get_standard_option
('pve-node'),
4107 snapname
=> get_standard_option
('pve-snapshot-name'),
4116 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4123 push @$res, { cmd
=> 'rollback' };
4124 push @$res, { cmd
=> 'config' };
4129 __PACKAGE__-
>register_method({
4130 name
=> 'update_snapshot_config',
4131 path
=> '{vmid}/snapshot/{snapname}/config',
4135 description
=> "Update snapshot metadata.",
4137 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4140 additionalProperties
=> 0,
4142 node
=> get_standard_option
('pve-node'),
4143 vmid
=> get_standard_option
('pve-vmid'),
4144 snapname
=> get_standard_option
('pve-snapshot-name'),
4148 description
=> "A textual description or comment.",
4152 returns
=> { type
=> 'null' },
4156 my $rpcenv = PVE
::RPCEnvironment
::get
();
4158 my $authuser = $rpcenv->get_user();
4160 my $vmid = extract_param
($param, 'vmid');
4162 my $snapname = extract_param
($param, 'snapname');
4164 return if !defined($param->{description
});
4166 my $updatefn = sub {
4168 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4170 PVE
::QemuConfig-
>check_lock($conf);
4172 my $snap = $conf->{snapshots
}->{$snapname};
4174 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4176 $snap->{description
} = $param->{description
} if defined($param->{description
});
4178 PVE
::QemuConfig-
>write_config($vmid, $conf);
4181 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4186 __PACKAGE__-
>register_method({
4187 name
=> 'get_snapshot_config',
4188 path
=> '{vmid}/snapshot/{snapname}/config',
4191 description
=> "Get snapshot configuration",
4193 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4196 additionalProperties
=> 0,
4198 node
=> get_standard_option
('pve-node'),
4199 vmid
=> get_standard_option
('pve-vmid'),
4200 snapname
=> get_standard_option
('pve-snapshot-name'),
4203 returns
=> { type
=> "object" },
4207 my $rpcenv = PVE
::RPCEnvironment
::get
();
4209 my $authuser = $rpcenv->get_user();
4211 my $vmid = extract_param
($param, 'vmid');
4213 my $snapname = extract_param
($param, 'snapname');
4215 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4217 my $snap = $conf->{snapshots
}->{$snapname};
4219 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4224 __PACKAGE__-
>register_method({
4226 path
=> '{vmid}/snapshot/{snapname}/rollback',
4230 description
=> "Rollback VM state to specified snapshot.",
4232 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4235 additionalProperties
=> 0,
4237 node
=> get_standard_option
('pve-node'),
4238 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4239 snapname
=> get_standard_option
('pve-snapshot-name'),
4244 description
=> "the task ID.",
4249 my $rpcenv = PVE
::RPCEnvironment
::get
();
4251 my $authuser = $rpcenv->get_user();
4253 my $node = extract_param
($param, 'node');
4255 my $vmid = extract_param
($param, 'vmid');
4257 my $snapname = extract_param
($param, 'snapname');
4260 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4261 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4265 # hold migration lock, this makes sure that nobody create replication snapshots
4266 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4269 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4272 __PACKAGE__-
>register_method({
4273 name
=> 'delsnapshot',
4274 path
=> '{vmid}/snapshot/{snapname}',
4278 description
=> "Delete a VM snapshot.",
4280 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4283 additionalProperties
=> 0,
4285 node
=> get_standard_option
('pve-node'),
4286 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4287 snapname
=> get_standard_option
('pve-snapshot-name'),
4291 description
=> "For removal from config file, even if removing disk snapshots fails.",
4297 description
=> "the task ID.",
4302 my $rpcenv = PVE
::RPCEnvironment
::get
();
4304 my $authuser = $rpcenv->get_user();
4306 my $node = extract_param
($param, 'node');
4308 my $vmid = extract_param
($param, 'vmid');
4310 my $snapname = extract_param
($param, 'snapname');
4313 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4314 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4317 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4320 __PACKAGE__-
>register_method({
4322 path
=> '{vmid}/template',
4326 description
=> "Create a Template.",
4328 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4329 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4332 additionalProperties
=> 0,
4334 node
=> get_standard_option
('pve-node'),
4335 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4339 description
=> "If you want to convert only 1 disk to base image.",
4340 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4347 description
=> "the task ID.",
4352 my $rpcenv = PVE
::RPCEnvironment
::get
();
4354 my $authuser = $rpcenv->get_user();
4356 my $node = extract_param
($param, 'node');
4358 my $vmid = extract_param
($param, 'vmid');
4360 my $disk = extract_param
($param, 'disk');
4362 my $load_and_check = sub {
4363 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4365 PVE
::QemuConfig-
>check_lock($conf);
4367 die "unable to create template, because VM contains snapshots\n"
4368 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4370 die "you can't convert a template to a template\n"
4371 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4373 die "you can't convert a VM to template if VM is running\n"
4374 if PVE
::QemuServer
::check_running
($vmid);
4379 $load_and_check->();
4382 PVE
::QemuConfig-
>lock_config($vmid, sub {
4383 my $conf = $load_and_check->();
4385 $conf->{template
} = 1;
4386 PVE
::QemuConfig-
>write_config($vmid, $conf);
4388 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4392 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4395 __PACKAGE__-
>register_method({
4396 name
=> 'cloudinit_generated_config_dump',
4397 path
=> '{vmid}/cloudinit/dump',
4400 description
=> "Get automatically generated cloudinit config.",
4402 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4405 additionalProperties
=> 0,
4407 node
=> get_standard_option
('pve-node'),
4408 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4410 description
=> 'Config type.',
4412 enum
=> ['user', 'network', 'meta'],
4422 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4424 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});