1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::RPCEnvironment
;
29 use PVE
::AccessControl
;
33 use PVE
::API2
::Firewall
::VM
;
34 use PVE
::API2
::Qemu
::Agent
;
35 use PVE
::VZDump
::Plugin
;
36 use PVE
::DataCenterConfig
;
40 if (!$ENV{PVE_GENERATING_DOCS
}) {
41 require PVE
::HA
::Env
::PVE2
;
42 import PVE
::HA
::Env
::PVE2
;
43 require PVE
::HA
::Config
;
44 import PVE
::HA
::Config
;
48 use Data
::Dumper
; # fixme: remove
50 use base
qw(PVE::RESTHandler);
52 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
54 my $resolve_cdrom_alias = sub {
57 if (my $value = $param->{cdrom
}) {
58 $value .= ",media=cdrom" if $value !~ m/media=/;
59 $param->{ide2
} = $value;
60 delete $param->{cdrom
};
64 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
65 my $check_storage_access = sub {
66 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
68 PVE
::QemuConfig-
>foreach_volume($settings, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
74 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
76 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
78 } elsif ($isCDROM && ($volid eq 'cdrom')) {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
81 my ($storeid, $size) = ($2 || $default_storage, $3);
82 die "no storage ID specified (and no default storage)\n" if !$storeid;
83 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
85 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
86 if !$scfg->{content
}->{images
};
88 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
92 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
93 if defined($settings->{vmstatestorage
});
96 my $check_storage_access_clone = sub {
97 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
101 PVE
::QemuConfig-
>foreach_volume($conf, sub {
102 my ($ds, $drive) = @_;
104 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
106 my $volid = $drive->{file
};
108 return if !$volid || $volid eq 'none';
111 if ($volid eq 'cdrom') {
112 $rpcenv->check($authuser, "/", ['Sys.Console']);
114 # we simply allow access
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
121 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
122 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
123 $sharedvm = 0 if !$scfg->{shared
};
125 $sid = $storage if $storage;
126 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
130 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
131 if defined($conf->{vmstatestorage
});
136 # Note: $pool is only needed when creating a VM, because pool permissions
137 # are automatically inherited if VM already exists inside a pool.
138 my $create_disks = sub {
139 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
146 my ($ds, $disk) = @_;
148 my $volid = $disk->{file
};
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
151 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
152 delete $disk->{size
};
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
154 } elsif (defined($volname) && $volname eq 'cloudinit') {
155 $storeid = $storeid // $default_storage;
156 die "no storage ID specified (and no default storage)\n" if !$storeid;
157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
158 my $name = "vm-$vmid-cloudinit";
162 $fmt = $disk->{format
} // "qcow2";
165 $fmt = $disk->{format
} // "raw";
168 # Initial disk created with 4 MB and aligned to 4MB on regeneration
169 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
170 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
171 $disk->{file
} = $volid;
172 $disk->{media
} = 'cdrom';
173 push @$vollist, $volid;
174 delete $disk->{format
}; # no longer needed
175 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
176 } elsif ($volid =~ $NEW_DISK_RE) {
177 my ($storeid, $size) = ($2 || $default_storage, $3);
178 die "no storage ID specified (and no default storage)\n" if !$storeid;
179 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
180 my $fmt = $disk->{format
} || $defformat;
182 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
185 if ($ds eq 'efidisk0') {
186 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
187 } elsif ($ds eq 'tpmstate0') {
188 # swtpm can only use raw volumes, and uses a fixed size
189 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
190 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
191 "raw", undef, $size);
193 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
195 push @$vollist, $volid;
196 $disk->{file
} = $volid;
197 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
198 delete $disk->{format
}; # no longer needed
199 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
202 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
204 my $volid_is_new = 1;
207 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
208 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
213 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
215 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
217 die "volume $volid does not exist\n" if !$size;
219 $disk->{size
} = $size;
222 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
226 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
228 # free allocated images on error
230 syslog
('err', "VM $vmid creating disks failed");
231 foreach my $volid (@$vollist) {
232 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
238 # modify vm config if everything went well
239 foreach my $ds (keys %$res) {
240 $conf->{$ds} = $res->{$ds};
246 my $check_cpu_model_access = sub {
247 my ($rpcenv, $authuser, $new, $existing) = @_;
249 return if !defined($new->{cpu
});
251 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
252 return if !$cpu || !$cpu->{cputype
}; # always allow default
253 my $cputype = $cpu->{cputype
};
255 if ($existing && $existing->{cpu
}) {
256 # changing only other settings doesn't require permissions for CPU model
257 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
258 return if $existingCpu->{cputype
} eq $cputype;
261 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
262 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
277 my $memoryoptions = {
283 my $hwtypeoptions = {
296 my $generaloptions = {
303 'migrate_downtime' => 1,
304 'migrate_speed' => 1,
317 my $vmpoweroptions = {
324 'vmstatestorage' => 1,
327 my $cloudinitoptions = {
337 my $check_vm_create_serial_perm = sub {
338 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
340 return 1 if $authuser eq 'root@pam';
342 foreach my $opt (keys %{$param}) {
343 next if $opt !~ m/^serial\d+$/;
345 if ($param->{$opt} eq 'socket') {
346 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
348 die "only root can set '$opt' config for real devices\n";
355 my $check_vm_create_usb_perm = sub {
356 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
358 return 1 if $authuser eq 'root@pam';
360 foreach my $opt (keys %{$param}) {
361 next if $opt !~ m/^usb\d+$/;
363 if ($param->{$opt} =~ m/spice/) {
364 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
366 die "only root can set '$opt' config for real devices\n";
373 my $check_vm_modify_config_perm = sub {
374 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
376 return 1 if $authuser eq 'root@pam';
378 foreach my $opt (@$key_list) {
379 # some checks (e.g., disk, serial port, usb) need to be done somewhere
380 # else, as there the permission can be value dependend
381 next if PVE
::QemuServer
::is_valid_drivename
($opt);
382 next if $opt eq 'cdrom';
383 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
386 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
387 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
388 } elsif ($memoryoptions->{$opt}) {
389 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
390 } elsif ($hwtypeoptions->{$opt}) {
391 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
392 } elsif ($generaloptions->{$opt}) {
393 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
394 # special case for startup since it changes host behaviour
395 if ($opt eq 'startup') {
396 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
398 } elsif ($vmpoweroptions->{$opt}) {
399 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
400 } elsif ($diskoptions->{$opt}) {
401 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
402 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
403 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
404 } elsif ($cloudinitoptions->{$opt}) {
405 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
406 } elsif ($opt eq 'vmstate') {
407 # the user needs Disk and PowerMgmt privileges to change the vmstate
408 # also needs privileges on the storage, that will be checked later
409 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
411 # catches hostpci\d+, args, lock, etc.
412 # new options will be checked here
413 die "only root can set '$opt' config\n";
420 __PACKAGE__-
>register_method({
424 description
=> "Virtual machine index (per node).",
426 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
430 protected
=> 1, # qemu pid files are only readable by root
432 additionalProperties
=> 0,
434 node
=> get_standard_option
('pve-node'),
438 description
=> "Determine the full status of active VMs.",
446 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
448 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
453 my $rpcenv = PVE
::RPCEnvironment
::get
();
454 my $authuser = $rpcenv->get_user();
456 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
459 foreach my $vmid (keys %$vmstatus) {
460 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
462 my $data = $vmstatus->{$vmid};
469 my $parse_restore_archive = sub {
470 my ($storecfg, $archive) = @_;
472 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
474 if (defined($archive_storeid)) {
475 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
476 if ($scfg->{type
} eq 'pbs') {
483 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
491 __PACKAGE__-
>register_method({
495 description
=> "Create or restore a virtual machine.",
497 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
498 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
499 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
500 user
=> 'all', # check inside
505 additionalProperties
=> 0,
506 properties
=> PVE
::QemuServer
::json_config_properties
(
508 node
=> get_standard_option
('pve-node'),
509 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
511 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.",
515 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
517 storage
=> get_standard_option
('pve-storage-id', {
518 description
=> "Default storage.",
520 completion
=> \
&PVE
::QemuServer
::complete_storage
,
525 description
=> "Allow to overwrite existing VM.",
526 requires
=> 'archive',
531 description
=> "Assign a unique random ethernet address.",
532 requires
=> 'archive',
537 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
538 requires
=> 'archive',
542 type
=> 'string', format
=> 'pve-poolid',
543 description
=> "Add the VM to the specified pool.",
546 description
=> "Override I/O bandwidth limit (in KiB/s).",
550 default => 'restore limit from datacenter or storage config',
556 description
=> "Start VM after it was created successfully.",
566 my $rpcenv = PVE
::RPCEnvironment
::get
();
567 my $authuser = $rpcenv->get_user();
569 my $node = extract_param
($param, 'node');
570 my $vmid = extract_param
($param, 'vmid');
572 my $archive = extract_param
($param, 'archive');
573 my $is_restore = !!$archive;
575 my $bwlimit = extract_param
($param, 'bwlimit');
576 my $force = extract_param
($param, 'force');
577 my $pool = extract_param
($param, 'pool');
578 my $start_after_create = extract_param
($param, 'start');
579 my $storage = extract_param
($param, 'storage');
580 my $unique = extract_param
($param, 'unique');
581 my $live_restore = extract_param
($param, 'live-restore');
583 if (defined(my $ssh_keys = $param->{sshkeys
})) {
584 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
585 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
588 PVE
::Cluster
::check_cfs_quorum
();
590 my $filename = PVE
::QemuConfig-
>config_file($vmid);
591 my $storecfg = PVE
::Storage
::config
();
593 if (defined($pool)) {
594 $rpcenv->check_pool_exist($pool);
597 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
598 if defined($storage);
600 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
602 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
604 } elsif ($archive && $force && (-f
$filename) &&
605 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
606 # OK: user has VM.Backup permissions, and want to restore an existing VM
612 &$resolve_cdrom_alias($param);
614 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
616 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
618 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
619 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
621 &$check_cpu_model_access($rpcenv, $authuser, $param);
623 foreach my $opt (keys %$param) {
624 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
625 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
626 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
628 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
629 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
633 PVE
::QemuServer
::add_random_macs
($param);
635 my $keystr = join(' ', keys %$param);
636 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
638 if ($archive eq '-') {
639 die "pipe requires cli environment\n"
640 if $rpcenv->{type
} ne 'cli';
641 $archive = { type
=> 'pipe' };
643 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
645 $archive = $parse_restore_archive->($storecfg, $archive);
649 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
651 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
652 die "$emsg $@" if $@;
654 my $restored_data = 0;
655 my $restorefn = sub {
656 my $conf = PVE
::QemuConfig-
>load_config($vmid);
658 PVE
::QemuConfig-
>check_protection($conf, $emsg);
660 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
663 my $restore_options = {
668 live
=> $live_restore,
670 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
671 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
673 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
674 } elsif ($archive->{type
} eq 'pbs') {
675 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
677 die "unknown backup archive type\n";
681 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
682 # Convert restored VM to template if backup was VM template
683 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
684 warn "Convert to template.\n";
685 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
690 # ensure no old replication state are exists
691 PVE
::ReplicationState
::delete_guest_states
($vmid);
693 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
695 if ($start_after_create && !$live_restore) {
696 print "Execute autostart\n";
697 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
703 # ensure no old replication state are exists
704 PVE
::ReplicationState
::delete_guest_states
($vmid);
708 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
712 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
714 if (!$conf->{boot
}) {
715 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
716 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
719 # auto generate uuid if user did not specify smbios1 option
720 if (!$conf->{smbios1
}) {
721 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
724 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
725 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
728 my $machine = $conf->{machine
};
729 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
730 # always pin Windows' machine version on create, they get to easily confused
731 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
732 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
736 PVE
::QemuConfig-
>write_config($vmid, $conf);
742 foreach my $volid (@$vollist) {
743 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
749 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
752 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
754 if ($start_after_create) {
755 print "Execute autostart\n";
756 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
761 my ($code, $worker_name);
763 $worker_name = 'qmrestore';
765 eval { $restorefn->() };
767 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
769 if ($restored_data) {
770 warn "error after data was restored, VM disks should be OK but config may "
771 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
773 warn "error before or during data restore, some or all disks were not "
774 ."completely restored. VM $vmid state is NOT cleaned up.\n";
780 $worker_name = 'qmcreate';
782 eval { $createfn->() };
785 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
786 unlink($conffile) or die "failed to remove config file: $!\n";
794 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
797 __PACKAGE__-
>register_method({
802 description
=> "Directory index",
807 additionalProperties
=> 0,
809 node
=> get_standard_option
('pve-node'),
810 vmid
=> get_standard_option
('pve-vmid'),
818 subdir
=> { type
=> 'string' },
821 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
827 { subdir
=> 'config' },
828 { subdir
=> 'pending' },
829 { subdir
=> 'status' },
830 { subdir
=> 'unlink' },
831 { subdir
=> 'vncproxy' },
832 { subdir
=> 'termproxy' },
833 { subdir
=> 'migrate' },
834 { subdir
=> 'resize' },
835 { subdir
=> 'move' },
837 { subdir
=> 'rrddata' },
838 { subdir
=> 'monitor' },
839 { subdir
=> 'agent' },
840 { subdir
=> 'snapshot' },
841 { subdir
=> 'spiceproxy' },
842 { subdir
=> 'sendkey' },
843 { subdir
=> 'firewall' },
849 __PACKAGE__-
>register_method ({
850 subclass
=> "PVE::API2::Firewall::VM",
851 path
=> '{vmid}/firewall',
854 __PACKAGE__-
>register_method ({
855 subclass
=> "PVE::API2::Qemu::Agent",
856 path
=> '{vmid}/agent',
859 __PACKAGE__-
>register_method({
861 path
=> '{vmid}/rrd',
863 protected
=> 1, # fixme: can we avoid that?
865 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
867 description
=> "Read VM RRD statistics (returns PNG)",
869 additionalProperties
=> 0,
871 node
=> get_standard_option
('pve-node'),
872 vmid
=> get_standard_option
('pve-vmid'),
874 description
=> "Specify the time frame you are interested in.",
876 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
879 description
=> "The list of datasources you want to display.",
880 type
=> 'string', format
=> 'pve-configid-list',
883 description
=> "The RRD consolidation function",
885 enum
=> [ 'AVERAGE', 'MAX' ],
893 filename
=> { type
=> 'string' },
899 return PVE
::RRD
::create_rrd_graph
(
900 "pve2-vm/$param->{vmid}", $param->{timeframe
},
901 $param->{ds
}, $param->{cf
});
905 __PACKAGE__-
>register_method({
907 path
=> '{vmid}/rrddata',
909 protected
=> 1, # fixme: can we avoid that?
911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
913 description
=> "Read VM RRD statistics",
915 additionalProperties
=> 0,
917 node
=> get_standard_option
('pve-node'),
918 vmid
=> get_standard_option
('pve-vmid'),
920 description
=> "Specify the time frame you are interested in.",
922 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
925 description
=> "The RRD consolidation function",
927 enum
=> [ 'AVERAGE', 'MAX' ],
942 return PVE
::RRD
::create_rrd_data
(
943 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
947 __PACKAGE__-
>register_method({
949 path
=> '{vmid}/config',
952 description
=> "Get the virtual machine configuration with pending configuration " .
953 "changes applied. Set the 'current' parameter to get the current configuration instead.",
955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
958 additionalProperties
=> 0,
960 node
=> get_standard_option
('pve-node'),
961 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
963 description
=> "Get current values (instead of pending values).",
968 snapshot
=> get_standard_option
('pve-snapshot-name', {
969 description
=> "Fetch config values from given snapshot.",
972 my ($cmd, $pname, $cur, $args) = @_;
973 PVE
::QemuConfig-
>snapshot_list($args->[0]);
979 description
=> "The VM configuration.",
981 properties
=> PVE
::QemuServer
::json_config_properties
({
984 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
991 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
992 current
=> "cannot use 'snapshot' parameter with 'current'"})
993 if ($param->{snapshot
} && $param->{current
});
996 if ($param->{snapshot
}) {
997 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
999 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1001 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1006 __PACKAGE__-
>register_method({
1007 name
=> 'vm_pending',
1008 path
=> '{vmid}/pending',
1011 description
=> "Get the virtual machine configuration with both current and pending values.",
1013 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1016 additionalProperties
=> 0,
1018 node
=> get_standard_option
('pve-node'),
1019 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1028 description
=> "Configuration option name.",
1032 description
=> "Current value.",
1037 description
=> "Pending value.",
1042 description
=> "Indicates a pending delete request if present and not 0. " .
1043 "The value 2 indicates a force-delete request.",
1055 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1057 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1059 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1060 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1062 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1065 # POST/PUT {vmid}/config implementation
1067 # The original API used PUT (idempotent) an we assumed that all operations
1068 # are fast. But it turned out that almost any configuration change can
1069 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1070 # time to complete and have side effects (not idempotent).
1072 # The new implementation uses POST and forks a worker process. We added
1073 # a new option 'background_delay'. If specified we wait up to
1074 # 'background_delay' second for the worker task to complete. It returns null
1075 # if the task is finished within that time, else we return the UPID.
1077 my $update_vm_api = sub {
1078 my ($param, $sync) = @_;
1080 my $rpcenv = PVE
::RPCEnvironment
::get
();
1082 my $authuser = $rpcenv->get_user();
1084 my $node = extract_param
($param, 'node');
1086 my $vmid = extract_param
($param, 'vmid');
1088 my $digest = extract_param
($param, 'digest');
1090 my $background_delay = extract_param
($param, 'background_delay');
1092 if (defined(my $cipassword = $param->{cipassword
})) {
1093 # Same logic as in cloud-init (but with the regex fixed...)
1094 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1095 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1098 my @paramarr = (); # used for log message
1099 foreach my $key (sort keys %$param) {
1100 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1101 push @paramarr, "-$key", $value;
1104 my $skiplock = extract_param
($param, 'skiplock');
1105 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1106 if $skiplock && $authuser ne 'root@pam';
1108 my $delete_str = extract_param
($param, 'delete');
1110 my $revert_str = extract_param
($param, 'revert');
1112 my $force = extract_param
($param, 'force');
1114 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1115 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1116 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1119 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1121 my $storecfg = PVE
::Storage
::config
();
1123 my $defaults = PVE
::QemuServer
::load_defaults
();
1125 &$resolve_cdrom_alias($param);
1127 # now try to verify all parameters
1130 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1131 if (!PVE
::QemuServer
::option_exists
($opt)) {
1132 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1135 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1136 "-revert $opt' at the same time" })
1137 if defined($param->{$opt});
1139 $revert->{$opt} = 1;
1143 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1144 $opt = 'ide2' if $opt eq 'cdrom';
1146 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1147 "-delete $opt' at the same time" })
1148 if defined($param->{$opt});
1150 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1151 "-revert $opt' at the same time" })
1154 if (!PVE
::QemuServer
::option_exists
($opt)) {
1155 raise_param_exc
({ delete => "unknown option '$opt'" });
1161 my $repl_conf = PVE
::ReplicationConfig-
>new();
1162 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1163 my $check_replication = sub {
1165 return if !$is_replicated;
1166 my $volid = $drive->{file
};
1167 return if !$volid || !($drive->{replicate
}//1);
1168 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1170 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1171 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1172 if !defined($storeid);
1174 return if defined($volname) && $volname eq 'cloudinit';
1177 if ($volid =~ $NEW_DISK_RE) {
1179 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1181 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1183 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1184 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1185 return if $scfg->{shared
};
1186 die "cannot add non-replicatable volume to a replicated VM\n";
1189 foreach my $opt (keys %$param) {
1190 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1191 # cleanup drive path
1192 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1193 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1194 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1195 $check_replication->($drive);
1196 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1197 } elsif ($opt =~ m/^net(\d+)$/) {
1199 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1200 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1201 } elsif ($opt eq 'vmgenid') {
1202 if ($param->{$opt} eq '1') {
1203 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1205 } elsif ($opt eq 'hookscript') {
1206 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1207 raise_param_exc
({ $opt => $@ }) if $@;
1211 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1213 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1215 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1217 my $updatefn = sub {
1219 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1221 die "checksum missmatch (file change by other user?)\n"
1222 if $digest && $digest ne $conf->{digest
};
1224 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1226 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1227 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1228 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1229 delete $conf->{lock}; # for check lock check, not written out
1230 push @delete, 'lock'; # this is the real deal to write it out
1232 push @delete, 'runningmachine' if $conf->{runningmachine
};
1233 push @delete, 'runningcpu' if $conf->{runningcpu
};
1236 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1238 foreach my $opt (keys %$revert) {
1239 if (defined($conf->{$opt})) {
1240 $param->{$opt} = $conf->{$opt};
1241 } elsif (defined($conf->{pending
}->{$opt})) {
1246 if ($param->{memory
} || defined($param->{balloon
})) {
1247 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1248 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1250 die "balloon value too large (must be smaller than assigned memory)\n"
1251 if $balloon && $balloon > $maxmem;
1254 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1258 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1260 # write updates to pending section
1262 my $modified = {}; # record what $option we modify
1265 if (my $boot = $conf->{boot
}) {
1266 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1267 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1269 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1271 my $check_drive_perms = sub {
1272 my ($opt, $val) = @_;
1273 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1274 # FIXME: cloudinit: CDROM or Disk?
1275 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1276 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1278 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1282 foreach my $opt (@delete) {
1283 $modified->{$opt} = 1;
1284 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1286 # value of what we want to delete, independent if pending or not
1287 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1288 if (!defined($val)) {
1289 warn "cannot delete '$opt' - not set in current configuration!\n";
1290 $modified->{$opt} = 0;
1293 my $is_pending_val = defined($conf->{pending
}->{$opt});
1294 delete $conf->{pending
}->{$opt};
1296 # remove from bootorder if necessary
1297 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1298 @bootorder = grep {$_ ne $opt} @bootorder;
1299 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1300 $modified->{boot
} = 1;
1303 if ($opt =~ m/^unused/) {
1304 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1305 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1306 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1307 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1308 delete $conf->{$opt};
1309 PVE
::QemuConfig-
>write_config($vmid, $conf);
1311 } elsif ($opt eq 'vmstate') {
1312 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1313 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1314 delete $conf->{$opt};
1315 PVE
::QemuConfig-
>write_config($vmid, $conf);
1317 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1318 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1319 $check_drive_perms->($opt, $val);
1320 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1322 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1323 PVE
::QemuConfig-
>write_config($vmid, $conf);
1324 } elsif ($opt =~ m/^serial\d+$/) {
1325 if ($val eq 'socket') {
1326 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1327 } elsif ($authuser ne 'root@pam') {
1328 die "only root can delete '$opt' config for real devices\n";
1330 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1331 PVE
::QemuConfig-
>write_config($vmid, $conf);
1332 } elsif ($opt =~ m/^usb\d+$/) {
1333 if ($val =~ m/spice/) {
1334 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1335 } elsif ($authuser ne 'root@pam') {
1336 die "only root can delete '$opt' config for real devices\n";
1338 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1339 PVE
::QemuConfig-
>write_config($vmid, $conf);
1341 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1342 PVE
::QemuConfig-
>write_config($vmid, $conf);
1346 foreach my $opt (keys %$param) { # add/change
1347 $modified->{$opt} = 1;
1348 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1349 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1351 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1353 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1355 if ($conf->{$opt}) {
1356 $check_drive_perms->($opt, $conf->{$opt});
1360 $check_drive_perms->($opt, $param->{$opt});
1361 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1362 if defined($conf->{pending
}->{$opt});
1364 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1366 # default legacy boot order implies all cdroms anyway
1368 # append new CD drives to bootorder to mark them bootable
1369 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1370 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1371 push @bootorder, $opt;
1372 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1373 $modified->{boot
} = 1;
1376 } elsif ($opt =~ m/^serial\d+/) {
1377 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1378 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1379 } elsif ($authuser ne 'root@pam') {
1380 die "only root can modify '$opt' config for real devices\n";
1382 $conf->{pending
}->{$opt} = $param->{$opt};
1383 } elsif ($opt =~ m/^usb\d+/) {
1384 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1385 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1386 } elsif ($authuser ne 'root@pam') {
1387 die "only root can modify '$opt' config for real devices\n";
1389 $conf->{pending
}->{$opt} = $param->{$opt};
1391 $conf->{pending
}->{$opt} = $param->{$opt};
1393 if ($opt eq 'boot') {
1394 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1395 if ($new_bootcfg->{order
}) {
1396 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1397 for my $dev (@devs) {
1398 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1399 my $deleted = grep {$_ eq $dev} @delete;
1400 die "invalid bootorder: device '$dev' does not exist'\n"
1401 if !$exists || $deleted;
1404 # remove legacy boot order settings if new one set
1405 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1406 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1407 if $conf->{bootdisk
};
1411 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1412 PVE
::QemuConfig-
>write_config($vmid, $conf);
1415 # remove pending changes when nothing changed
1416 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1417 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1418 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1420 return if !scalar(keys %{$conf->{pending
}});
1422 my $running = PVE
::QemuServer
::check_running
($vmid);
1424 # apply pending changes
1426 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1430 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1432 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1434 raise_param_exc
($errors) if scalar(keys %$errors);
1443 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1445 if ($background_delay) {
1447 # Note: It would be better to do that in the Event based HTTPServer
1448 # to avoid blocking call to sleep.
1450 my $end_time = time() + $background_delay;
1452 my $task = PVE
::Tools
::upid_decode
($upid);
1455 while (time() < $end_time) {
1456 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1458 sleep(1); # this gets interrupted when child process ends
1462 my $status = PVE
::Tools
::upid_read_status
($upid);
1463 return if !PVE
::Tools
::upid_status_is_error
($status);
1472 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1475 my $vm_config_perm_list = [
1480 'VM.Config.Network',
1482 'VM.Config.Options',
1483 'VM.Config.Cloudinit',
1486 __PACKAGE__-
>register_method({
1487 name
=> 'update_vm_async',
1488 path
=> '{vmid}/config',
1492 description
=> "Set virtual machine options (asynchrounous API).",
1494 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1497 additionalProperties
=> 0,
1498 properties
=> PVE
::QemuServer
::json_config_properties
(
1500 node
=> get_standard_option
('pve-node'),
1501 vmid
=> get_standard_option
('pve-vmid'),
1502 skiplock
=> get_standard_option
('skiplock'),
1504 type
=> 'string', format
=> 'pve-configid-list',
1505 description
=> "A list of settings you want to delete.",
1509 type
=> 'string', format
=> 'pve-configid-list',
1510 description
=> "Revert a pending change.",
1515 description
=> $opt_force_description,
1517 requires
=> 'delete',
1521 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1525 background_delay
=> {
1527 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1538 code
=> $update_vm_api,
1541 __PACKAGE__-
>register_method({
1542 name
=> 'update_vm',
1543 path
=> '{vmid}/config',
1547 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1549 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1552 additionalProperties
=> 0,
1553 properties
=> PVE
::QemuServer
::json_config_properties
(
1555 node
=> get_standard_option
('pve-node'),
1556 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1557 skiplock
=> get_standard_option
('skiplock'),
1559 type
=> 'string', format
=> 'pve-configid-list',
1560 description
=> "A list of settings you want to delete.",
1564 type
=> 'string', format
=> 'pve-configid-list',
1565 description
=> "Revert a pending change.",
1570 description
=> $opt_force_description,
1572 requires
=> 'delete',
1576 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1582 returns
=> { type
=> 'null' },
1585 &$update_vm_api($param, 1);
1590 __PACKAGE__-
>register_method({
1591 name
=> 'destroy_vm',
1596 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1597 ." and firewall rules",
1599 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1602 additionalProperties
=> 0,
1604 node
=> get_standard_option
('pve-node'),
1605 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1606 skiplock
=> get_standard_option
('skiplock'),
1609 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1612 'destroy-unreferenced-disks' => {
1614 description
=> "If set, destroy additionally all disks not referenced in the config"
1615 ." but with a matching VMID from all enabled storages.",
1627 my $rpcenv = PVE
::RPCEnvironment
::get
();
1628 my $authuser = $rpcenv->get_user();
1629 my $vmid = $param->{vmid
};
1631 my $skiplock = $param->{skiplock
};
1632 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1633 if $skiplock && $authuser ne 'root@pam';
1635 my $early_checks = sub {
1637 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1638 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1640 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1642 if (!$param->{purge
}) {
1643 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1645 # don't allow destroy if with replication jobs but no purge param
1646 my $repl_conf = PVE
::ReplicationConfig-
>new();
1647 $repl_conf->check_for_existing_jobs($vmid);
1650 die "VM $vmid is running - destroy failed\n"
1651 if PVE
::QemuServer
::check_running
($vmid);
1661 my $storecfg = PVE
::Storage
::config
();
1663 syslog
('info', "destroy VM $vmid: $upid\n");
1664 PVE
::QemuConfig-
>lock_config($vmid, sub {
1665 # repeat, config might have changed
1666 my $ha_managed = $early_checks->();
1668 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1670 PVE
::QemuServer
::destroy_vm
(
1673 $skiplock, { lock => 'destroyed' },
1674 $purge_unreferenced,
1677 PVE
::AccessControl
::remove_vm_access
($vmid);
1678 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1679 if ($param->{purge
}) {
1680 print "purging VM $vmid from related configurations..\n";
1681 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1682 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1685 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1686 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1690 # only now remove the zombie config, else we can have reuse race
1691 PVE
::QemuConfig-
>destroy_config($vmid);
1695 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1698 __PACKAGE__-
>register_method({
1700 path
=> '{vmid}/unlink',
1704 description
=> "Unlink/delete disk images.",
1706 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1709 additionalProperties
=> 0,
1711 node
=> get_standard_option
('pve-node'),
1712 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1714 type
=> 'string', format
=> 'pve-configid-list',
1715 description
=> "A list of disk IDs you want to delete.",
1719 description
=> $opt_force_description,
1724 returns
=> { type
=> 'null'},
1728 $param->{delete} = extract_param
($param, 'idlist');
1730 __PACKAGE__-
>update_vm($param);
1735 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1736 my $gen_rand_chars = sub {
1739 die "invalid length $length" if $length < 1;
1741 my $min = ord('!'); # first printable ascii
1743 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1744 die "failed to generate random bytes!\n"
1747 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1754 __PACKAGE__-
>register_method({
1756 path
=> '{vmid}/vncproxy',
1760 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1762 description
=> "Creates a TCP VNC proxy connections.",
1764 additionalProperties
=> 0,
1766 node
=> get_standard_option
('pve-node'),
1767 vmid
=> get_standard_option
('pve-vmid'),
1771 description
=> "starts websockify instead of vncproxy",
1773 'generate-password' => {
1777 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1782 additionalProperties
=> 0,
1784 user
=> { type
=> 'string' },
1785 ticket
=> { type
=> 'string' },
1788 description
=> "Returned if requested with 'generate-password' param."
1789 ." Consists of printable ASCII characters ('!' .. '~').",
1792 cert
=> { type
=> 'string' },
1793 port
=> { type
=> 'integer' },
1794 upid
=> { type
=> 'string' },
1800 my $rpcenv = PVE
::RPCEnvironment
::get
();
1802 my $authuser = $rpcenv->get_user();
1804 my $vmid = $param->{vmid
};
1805 my $node = $param->{node
};
1806 my $websocket = $param->{websocket
};
1808 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1812 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1813 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1816 my $authpath = "/vms/$vmid";
1818 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1819 my $password = $ticket;
1820 if ($param->{'generate-password'}) {
1821 $password = $gen_rand_chars->(8);
1824 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1830 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1831 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1832 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1833 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1834 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1836 $family = PVE
::Tools
::get_host_address_family
($node);
1839 my $port = PVE
::Tools
::next_vnc_port
($family);
1846 syslog
('info', "starting vnc proxy $upid\n");
1850 if (defined($serial)) {
1852 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1854 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1855 '-timeout', $timeout, '-authpath', $authpath,
1856 '-perm', 'Sys.Console'];
1858 if ($param->{websocket
}) {
1859 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1860 push @$cmd, '-notls', '-listen', 'localhost';
1863 push @$cmd, '-c', @$remcmd, @$termcmd;
1865 PVE
::Tools
::run_command
($cmd);
1869 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1871 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1873 my $sock = IO
::Socket
::IP-
>new(
1878 GetAddrInfoFlags
=> 0,
1879 ) or die "failed to create socket: $!\n";
1880 # Inside the worker we shouldn't have any previous alarms
1881 # running anyway...:
1883 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1885 accept(my $cli, $sock) or die "connection failed: $!\n";
1888 if (PVE
::Tools
::run_command
($cmd,
1889 output
=> '>&'.fileno($cli),
1890 input
=> '<&'.fileno($cli),
1893 die "Failed to run vncproxy.\n";
1900 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1902 PVE
::Tools
::wait_for_vnc_port
($port);
1911 $res->{password
} = $password if $param->{'generate-password'};
1916 __PACKAGE__-
>register_method({
1917 name
=> 'termproxy',
1918 path
=> '{vmid}/termproxy',
1922 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1924 description
=> "Creates a TCP proxy connections.",
1926 additionalProperties
=> 0,
1928 node
=> get_standard_option
('pve-node'),
1929 vmid
=> get_standard_option
('pve-vmid'),
1933 enum
=> [qw(serial0 serial1 serial2 serial3)],
1934 description
=> "opens a serial terminal (defaults to display)",
1939 additionalProperties
=> 0,
1941 user
=> { type
=> 'string' },
1942 ticket
=> { type
=> 'string' },
1943 port
=> { type
=> 'integer' },
1944 upid
=> { type
=> 'string' },
1950 my $rpcenv = PVE
::RPCEnvironment
::get
();
1952 my $authuser = $rpcenv->get_user();
1954 my $vmid = $param->{vmid
};
1955 my $node = $param->{node
};
1956 my $serial = $param->{serial
};
1958 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1960 if (!defined($serial)) {
1962 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1963 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1967 my $authpath = "/vms/$vmid";
1969 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1974 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1975 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1976 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1977 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1978 push @$remcmd, '--';
1980 $family = PVE
::Tools
::get_host_address_family
($node);
1983 my $port = PVE
::Tools
::next_vnc_port
($family);
1985 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1986 push @$termcmd, '-iface', $serial if $serial;
1991 syslog
('info', "starting qemu termproxy $upid\n");
1993 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1994 '--perm', 'VM.Console', '--'];
1995 push @$cmd, @$remcmd, @$termcmd;
1997 PVE
::Tools
::run_command
($cmd);
2000 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2002 PVE
::Tools
::wait_for_vnc_port
($port);
2012 __PACKAGE__-
>register_method({
2013 name
=> 'vncwebsocket',
2014 path
=> '{vmid}/vncwebsocket',
2017 description
=> "You also need to pass a valid ticket (vncticket).",
2018 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2020 description
=> "Opens a weksocket for VNC traffic.",
2022 additionalProperties
=> 0,
2024 node
=> get_standard_option
('pve-node'),
2025 vmid
=> get_standard_option
('pve-vmid'),
2027 description
=> "Ticket from previous call to vncproxy.",
2032 description
=> "Port number returned by previous vncproxy call.",
2042 port
=> { type
=> 'string' },
2048 my $rpcenv = PVE
::RPCEnvironment
::get
();
2050 my $authuser = $rpcenv->get_user();
2052 my $vmid = $param->{vmid
};
2053 my $node = $param->{node
};
2055 my $authpath = "/vms/$vmid";
2057 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2059 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2061 # Note: VNC ports are acessible from outside, so we do not gain any
2062 # security if we verify that $param->{port} belongs to VM $vmid. This
2063 # check is done by verifying the VNC ticket (inside VNC protocol).
2065 my $port = $param->{port
};
2067 return { port
=> $port };
2070 __PACKAGE__-
>register_method({
2071 name
=> 'spiceproxy',
2072 path
=> '{vmid}/spiceproxy',
2077 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2079 description
=> "Returns a SPICE configuration to connect to the VM.",
2081 additionalProperties
=> 0,
2083 node
=> get_standard_option
('pve-node'),
2084 vmid
=> get_standard_option
('pve-vmid'),
2085 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2088 returns
=> get_standard_option
('remote-viewer-config'),
2092 my $rpcenv = PVE
::RPCEnvironment
::get
();
2094 my $authuser = $rpcenv->get_user();
2096 my $vmid = $param->{vmid
};
2097 my $node = $param->{node
};
2098 my $proxy = $param->{proxy
};
2100 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2101 my $title = "VM $vmid";
2102 $title .= " - ". $conf->{name
} if $conf->{name
};
2104 my $port = PVE
::QemuServer
::spice_port
($vmid);
2106 my ($ticket, undef, $remote_viewer_config) =
2107 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2109 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2110 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2112 return $remote_viewer_config;
2115 __PACKAGE__-
>register_method({
2117 path
=> '{vmid}/status',
2120 description
=> "Directory index",
2125 additionalProperties
=> 0,
2127 node
=> get_standard_option
('pve-node'),
2128 vmid
=> get_standard_option
('pve-vmid'),
2136 subdir
=> { type
=> 'string' },
2139 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2145 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2148 { subdir
=> 'current' },
2149 { subdir
=> 'start' },
2150 { subdir
=> 'stop' },
2151 { subdir
=> 'reset' },
2152 { subdir
=> 'shutdown' },
2153 { subdir
=> 'suspend' },
2154 { subdir
=> 'reboot' },
2160 __PACKAGE__-
>register_method({
2161 name
=> 'vm_status',
2162 path
=> '{vmid}/status/current',
2165 protected
=> 1, # qemu pid files are only readable by root
2166 description
=> "Get virtual machine status.",
2168 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2171 additionalProperties
=> 0,
2173 node
=> get_standard_option
('pve-node'),
2174 vmid
=> get_standard_option
('pve-vmid'),
2180 %$PVE::QemuServer
::vmstatus_return_properties
,
2182 description
=> "HA manager service status.",
2186 description
=> "Qemu VGA configuration supports spice.",
2191 description
=> "Qemu GuestAgent enabled in config.",
2201 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2203 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2204 my $status = $vmstatus->{$param->{vmid
}};
2206 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2208 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2209 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2214 __PACKAGE__-
>register_method({
2216 path
=> '{vmid}/status/start',
2220 description
=> "Start virtual machine.",
2222 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2225 additionalProperties
=> 0,
2227 node
=> get_standard_option
('pve-node'),
2228 vmid
=> get_standard_option
('pve-vmid',
2229 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2230 skiplock
=> get_standard_option
('skiplock'),
2231 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2232 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2235 enum
=> ['secure', 'insecure'],
2236 description
=> "Migration traffic is encrypted using an SSH " .
2237 "tunnel by default. On secure, completely private networks " .
2238 "this can be disabled to increase performance.",
2241 migration_network
=> {
2242 type
=> 'string', format
=> 'CIDR',
2243 description
=> "CIDR of the (sub) network that is used for migration.",
2246 machine
=> get_standard_option
('pve-qemu-machine'),
2248 description
=> "Override QEMU's -cpu argument with the given string.",
2252 targetstorage
=> get_standard_option
('pve-targetstorage'),
2254 description
=> "Wait maximal timeout seconds.",
2257 default => 'max(30, vm memory in GiB)',
2268 my $rpcenv = PVE
::RPCEnvironment
::get
();
2269 my $authuser = $rpcenv->get_user();
2271 my $node = extract_param
($param, 'node');
2272 my $vmid = extract_param
($param, 'vmid');
2273 my $timeout = extract_param
($param, 'timeout');
2275 my $machine = extract_param
($param, 'machine');
2276 my $force_cpu = extract_param
($param, 'force-cpu');
2278 my $get_root_param = sub {
2279 my $value = extract_param
($param, $_[0]);
2280 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2281 if $value && $authuser ne 'root@pam';
2285 my $stateuri = $get_root_param->('stateuri');
2286 my $skiplock = $get_root_param->('skiplock');
2287 my $migratedfrom = $get_root_param->('migratedfrom');
2288 my $migration_type = $get_root_param->('migration_type');
2289 my $migration_network = $get_root_param->('migration_network');
2290 my $targetstorage = $get_root_param->('targetstorage');
2294 if ($targetstorage) {
2295 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2297 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2298 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2302 # read spice ticket from STDIN
2304 my $nbd_protocol_version = 0;
2305 my $replicated_volumes = {};
2306 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2307 while (defined(my $line = <STDIN
>)) {
2309 if ($line =~ m/^spice_ticket: (.+)$/) {
2311 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2312 $nbd_protocol_version = $1;
2313 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2314 $replicated_volumes->{$1} = 1;
2316 # fallback for old source node
2317 $spice_ticket = $line;
2322 PVE
::Cluster
::check_cfs_quorum
();
2324 my $storecfg = PVE
::Storage
::config
();
2326 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2330 print "Requesting HA start for VM $vmid\n";
2332 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2333 PVE
::Tools
::run_command
($cmd);
2337 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2344 syslog
('info', "start VM $vmid: $upid\n");
2346 my $migrate_opts = {
2347 migratedfrom
=> $migratedfrom,
2348 spice_ticket
=> $spice_ticket,
2349 network
=> $migration_network,
2350 type
=> $migration_type,
2351 storagemap
=> $storagemap,
2352 nbd_proto_version
=> $nbd_protocol_version,
2353 replicated_volumes
=> $replicated_volumes,
2357 statefile
=> $stateuri,
2358 skiplock
=> $skiplock,
2359 forcemachine
=> $machine,
2360 timeout
=> $timeout,
2361 forcecpu
=> $force_cpu,
2364 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2368 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2372 __PACKAGE__-
>register_method({
2374 path
=> '{vmid}/status/stop',
2378 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2379 "is akin to pulling the power plug of a running computer and may damage the VM data",
2381 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2384 additionalProperties
=> 0,
2386 node
=> get_standard_option
('pve-node'),
2387 vmid
=> get_standard_option
('pve-vmid',
2388 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2389 skiplock
=> get_standard_option
('skiplock'),
2390 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2392 description
=> "Wait maximal timeout seconds.",
2398 description
=> "Do not deactivate storage volumes.",
2411 my $rpcenv = PVE
::RPCEnvironment
::get
();
2412 my $authuser = $rpcenv->get_user();
2414 my $node = extract_param
($param, 'node');
2415 my $vmid = extract_param
($param, 'vmid');
2417 my $skiplock = extract_param
($param, 'skiplock');
2418 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2419 if $skiplock && $authuser ne 'root@pam';
2421 my $keepActive = extract_param
($param, 'keepActive');
2422 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2423 if $keepActive && $authuser ne 'root@pam';
2425 my $migratedfrom = extract_param
($param, 'migratedfrom');
2426 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2427 if $migratedfrom && $authuser ne 'root@pam';
2430 my $storecfg = PVE
::Storage
::config
();
2432 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2437 print "Requesting HA stop for VM $vmid\n";
2439 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2440 PVE
::Tools
::run_command
($cmd);
2444 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2450 syslog
('info', "stop VM $vmid: $upid\n");
2452 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2453 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2457 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2461 __PACKAGE__-
>register_method({
2463 path
=> '{vmid}/status/reset',
2467 description
=> "Reset virtual machine.",
2469 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2472 additionalProperties
=> 0,
2474 node
=> get_standard_option
('pve-node'),
2475 vmid
=> get_standard_option
('pve-vmid',
2476 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2477 skiplock
=> get_standard_option
('skiplock'),
2486 my $rpcenv = PVE
::RPCEnvironment
::get
();
2488 my $authuser = $rpcenv->get_user();
2490 my $node = extract_param
($param, 'node');
2492 my $vmid = extract_param
($param, 'vmid');
2494 my $skiplock = extract_param
($param, 'skiplock');
2495 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2496 if $skiplock && $authuser ne 'root@pam';
2498 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2503 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2508 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2511 __PACKAGE__-
>register_method({
2512 name
=> 'vm_shutdown',
2513 path
=> '{vmid}/status/shutdown',
2517 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2518 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2520 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2523 additionalProperties
=> 0,
2525 node
=> get_standard_option
('pve-node'),
2526 vmid
=> get_standard_option
('pve-vmid',
2527 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2528 skiplock
=> get_standard_option
('skiplock'),
2530 description
=> "Wait maximal timeout seconds.",
2536 description
=> "Make sure the VM stops.",
2542 description
=> "Do not deactivate storage volumes.",
2555 my $rpcenv = PVE
::RPCEnvironment
::get
();
2556 my $authuser = $rpcenv->get_user();
2558 my $node = extract_param
($param, 'node');
2559 my $vmid = extract_param
($param, 'vmid');
2561 my $skiplock = extract_param
($param, 'skiplock');
2562 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2563 if $skiplock && $authuser ne 'root@pam';
2565 my $keepActive = extract_param
($param, 'keepActive');
2566 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2567 if $keepActive && $authuser ne 'root@pam';
2569 my $storecfg = PVE
::Storage
::config
();
2573 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2574 # otherwise, we will infer a shutdown command, but run into the timeout,
2575 # then when the vm is resumed, it will instantly shutdown
2577 # checking the qmp status here to get feedback to the gui/cli/api
2578 # and the status query should not take too long
2579 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2580 if ($param->{forceStop
}) {
2581 warn "VM is paused - stop instead of shutdown\n";
2584 die "VM is paused - cannot shutdown\n";
2588 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2590 my $timeout = $param->{timeout
} // 60;
2594 print "Requesting HA stop for VM $vmid\n";
2596 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2597 PVE
::Tools
::run_command
($cmd);
2601 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2608 syslog
('info', "shutdown VM $vmid: $upid\n");
2610 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2611 $shutdown, $param->{forceStop
}, $keepActive);
2615 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2619 __PACKAGE__-
>register_method({
2620 name
=> 'vm_reboot',
2621 path
=> '{vmid}/status/reboot',
2625 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2627 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2630 additionalProperties
=> 0,
2632 node
=> get_standard_option
('pve-node'),
2633 vmid
=> get_standard_option
('pve-vmid',
2634 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2636 description
=> "Wait maximal timeout seconds for the shutdown.",
2649 my $rpcenv = PVE
::RPCEnvironment
::get
();
2650 my $authuser = $rpcenv->get_user();
2652 my $node = extract_param
($param, 'node');
2653 my $vmid = extract_param
($param, 'vmid');
2655 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2657 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2662 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2663 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2667 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2670 __PACKAGE__-
>register_method({
2671 name
=> 'vm_suspend',
2672 path
=> '{vmid}/status/suspend',
2676 description
=> "Suspend virtual machine.",
2678 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2679 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2680 " on the storage for the vmstate.",
2681 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2684 additionalProperties
=> 0,
2686 node
=> get_standard_option
('pve-node'),
2687 vmid
=> get_standard_option
('pve-vmid',
2688 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2689 skiplock
=> get_standard_option
('skiplock'),
2694 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2696 statestorage
=> get_standard_option
('pve-storage-id', {
2697 description
=> "The storage for the VM state",
2698 requires
=> 'todisk',
2700 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2710 my $rpcenv = PVE
::RPCEnvironment
::get
();
2711 my $authuser = $rpcenv->get_user();
2713 my $node = extract_param
($param, 'node');
2714 my $vmid = extract_param
($param, 'vmid');
2716 my $todisk = extract_param
($param, 'todisk') // 0;
2718 my $statestorage = extract_param
($param, 'statestorage');
2720 my $skiplock = extract_param
($param, 'skiplock');
2721 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2722 if $skiplock && $authuser ne 'root@pam';
2724 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2726 die "Cannot suspend HA managed VM to disk\n"
2727 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2729 # early check for storage permission, for better user feedback
2731 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2733 if (!$statestorage) {
2734 # get statestorage from config if none is given
2735 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2736 my $storecfg = PVE
::Storage
::config
();
2737 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2740 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2746 syslog
('info', "suspend VM $vmid: $upid\n");
2748 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2753 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2754 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2757 __PACKAGE__-
>register_method({
2758 name
=> 'vm_resume',
2759 path
=> '{vmid}/status/resume',
2763 description
=> "Resume virtual machine.",
2765 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2768 additionalProperties
=> 0,
2770 node
=> get_standard_option
('pve-node'),
2771 vmid
=> get_standard_option
('pve-vmid',
2772 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2773 skiplock
=> get_standard_option
('skiplock'),
2774 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2784 my $rpcenv = PVE
::RPCEnvironment
::get
();
2786 my $authuser = $rpcenv->get_user();
2788 my $node = extract_param
($param, 'node');
2790 my $vmid = extract_param
($param, 'vmid');
2792 my $skiplock = extract_param
($param, 'skiplock');
2793 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2794 if $skiplock && $authuser ne 'root@pam';
2796 my $nocheck = extract_param
($param, 'nocheck');
2797 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2798 if $nocheck && $authuser ne 'root@pam';
2800 my $to_disk_suspended;
2802 PVE
::QemuConfig-
>lock_config($vmid, sub {
2803 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2804 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2808 die "VM $vmid not running\n"
2809 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2814 syslog
('info', "resume VM $vmid: $upid\n");
2816 if (!$to_disk_suspended) {
2817 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2819 my $storecfg = PVE
::Storage
::config
();
2820 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2826 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2829 __PACKAGE__-
>register_method({
2830 name
=> 'vm_sendkey',
2831 path
=> '{vmid}/sendkey',
2835 description
=> "Send key event to virtual machine.",
2837 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2840 additionalProperties
=> 0,
2842 node
=> get_standard_option
('pve-node'),
2843 vmid
=> get_standard_option
('pve-vmid',
2844 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2845 skiplock
=> get_standard_option
('skiplock'),
2847 description
=> "The key (qemu monitor encoding).",
2852 returns
=> { type
=> 'null'},
2856 my $rpcenv = PVE
::RPCEnvironment
::get
();
2858 my $authuser = $rpcenv->get_user();
2860 my $node = extract_param
($param, 'node');
2862 my $vmid = extract_param
($param, 'vmid');
2864 my $skiplock = extract_param
($param, 'skiplock');
2865 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2866 if $skiplock && $authuser ne 'root@pam';
2868 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2873 __PACKAGE__-
>register_method({
2874 name
=> 'vm_feature',
2875 path
=> '{vmid}/feature',
2879 description
=> "Check if feature for virtual machine is available.",
2881 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2884 additionalProperties
=> 0,
2886 node
=> get_standard_option
('pve-node'),
2887 vmid
=> get_standard_option
('pve-vmid'),
2889 description
=> "Feature to check.",
2891 enum
=> [ 'snapshot', 'clone', 'copy' ],
2893 snapname
=> get_standard_option
('pve-snapshot-name', {
2901 hasFeature
=> { type
=> 'boolean' },
2904 items
=> { type
=> 'string' },
2911 my $node = extract_param
($param, 'node');
2913 my $vmid = extract_param
($param, 'vmid');
2915 my $snapname = extract_param
($param, 'snapname');
2917 my $feature = extract_param
($param, 'feature');
2919 my $running = PVE
::QemuServer
::check_running
($vmid);
2921 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2924 my $snap = $conf->{snapshots
}->{$snapname};
2925 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2928 my $storecfg = PVE
::Storage
::config
();
2930 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2931 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2934 hasFeature
=> $hasFeature,
2935 nodes
=> [ keys %$nodelist ],
2939 __PACKAGE__-
>register_method({
2941 path
=> '{vmid}/clone',
2945 description
=> "Create a copy of virtual machine/template.",
2947 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2948 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2949 "'Datastore.AllocateSpace' on any used storage.",
2952 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2954 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2955 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2960 additionalProperties
=> 0,
2962 node
=> get_standard_option
('pve-node'),
2963 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2964 newid
=> get_standard_option
('pve-vmid', {
2965 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2966 description
=> 'VMID for the clone.' }),
2969 type
=> 'string', format
=> 'dns-name',
2970 description
=> "Set a name for the new VM.",
2975 description
=> "Description for the new VM.",
2979 type
=> 'string', format
=> 'pve-poolid',
2980 description
=> "Add the new VM to the specified pool.",
2982 snapname
=> get_standard_option
('pve-snapshot-name', {
2985 storage
=> get_standard_option
('pve-storage-id', {
2986 description
=> "Target storage for full clone.",
2990 description
=> "Target format for file storage. Only valid for full clone.",
2993 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2998 description
=> "Create a full copy of all disks. This is always done when " .
2999 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3001 target
=> get_standard_option
('pve-node', {
3002 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3006 description
=> "Override I/O bandwidth limit (in KiB/s).",
3010 default => 'clone limit from datacenter or storage config',
3020 my $rpcenv = PVE
::RPCEnvironment
::get
();
3021 my $authuser = $rpcenv->get_user();
3023 my $node = extract_param
($param, 'node');
3024 my $vmid = extract_param
($param, 'vmid');
3025 my $newid = extract_param
($param, 'newid');
3026 my $pool = extract_param
($param, 'pool');
3027 $rpcenv->check_pool_exist($pool) if defined($pool);
3029 my $snapname = extract_param
($param, 'snapname');
3030 my $storage = extract_param
($param, 'storage');
3031 my $format = extract_param
($param, 'format');
3032 my $target = extract_param
($param, 'target');
3034 my $localnode = PVE
::INotify
::nodename
();
3036 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3040 PVE
::Cluster
::check_node_exists
($target) if $target;
3042 my $storecfg = PVE
::Storage
::config
();
3045 # check if storage is enabled on local node
3046 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3048 # check if storage is available on target node
3049 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3050 # clone only works if target storage is shared
3051 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3052 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3056 PVE
::Cluster
::check_cfs_quorum
();
3058 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3061 # do all tests after lock but before forking worker - if possible
3063 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3064 PVE
::QemuConfig-
>check_lock($conf);
3066 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3067 die "unexpected state change\n" if $verify_running != $running;
3069 die "snapshot '$snapname' does not exist\n"
3070 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3072 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3074 die "parameter 'storage' not allowed for linked clones\n"
3075 if defined($storage) && !$full;
3077 die "parameter 'format' not allowed for linked clones\n"
3078 if defined($format) && !$full;
3080 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3082 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3084 die "can't clone VM to node '$target' (VM uses local storage)\n"
3085 if $target && !$sharedvm;
3087 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3088 die "unable to create VM $newid: config file already exists\n"
3091 my $newconf = { lock => 'clone' };
3096 foreach my $opt (keys %$oldconf) {
3097 my $value = $oldconf->{$opt};
3099 # do not copy snapshot related info
3100 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3101 $opt eq 'vmstate' || $opt eq 'snapstate';
3103 # no need to copy unused images, because VMID(owner) changes anyways
3104 next if $opt =~ m/^unused\d+$/;
3106 # always change MAC! address
3107 if ($opt =~ m/^net(\d+)$/) {
3108 my $net = PVE
::QemuServer
::parse_net
($value);
3109 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3110 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3111 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3112 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3113 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3114 die "unable to parse drive options for '$opt'\n" if !$drive;
3115 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3116 $newconf->{$opt} = $value; # simply copy configuration
3118 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3119 die "Full clone feature is not supported for drive '$opt'\n"
3120 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3121 $fullclone->{$opt} = 1;
3123 # not full means clone instead of copy
3124 die "Linked clone feature is not supported for drive '$opt'\n"
3125 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3127 $drives->{$opt} = $drive;
3128 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3129 push @$vollist, $drive->{file
};
3132 # copy everything else
3133 $newconf->{$opt} = $value;
3137 # auto generate a new uuid
3138 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3139 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3140 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3141 # auto generate a new vmgenid only if the option was set for template
3142 if ($newconf->{vmgenid
}) {
3143 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3146 delete $newconf->{template
};
3148 if ($param->{name
}) {
3149 $newconf->{name
} = $param->{name
};
3151 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3154 if ($param->{description
}) {
3155 $newconf->{description
} = $param->{description
};
3158 # create empty/temp config - this fails if VM already exists on other node
3159 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3160 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3165 my $newvollist = [];
3172 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3174 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3176 my $bwlimit = extract_param
($param, 'bwlimit');
3178 my $total_jobs = scalar(keys %{$drives});
3181 foreach my $opt (sort keys %$drives) {
3182 my $drive = $drives->{$opt};
3183 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3184 my $completion = $skipcomplete ?
'skip' : 'complete';
3186 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3187 my $storage_list = [ $src_sid ];
3188 push @$storage_list, $storage if defined($storage);
3189 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3191 my $newdrive = PVE
::QemuServer
::clone_disk
(
3210 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3212 PVE
::QemuConfig-
>write_config($newid, $newconf);
3216 delete $newconf->{lock};
3218 # do not write pending changes
3219 if (my @changes = keys %{$newconf->{pending
}}) {
3220 my $pending = join(',', @changes);
3221 warn "found pending changes for '$pending', discarding for clone\n";
3222 delete $newconf->{pending
};
3225 PVE
::QemuConfig-
>write_config($newid, $newconf);
3228 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3229 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3230 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3232 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3233 die "Failed to move config to node '$target' - rename failed: $!\n"
3234 if !rename($conffile, $newconffile);
3237 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3240 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3241 sleep 1; # some storage like rbd need to wait before release volume - really?
3243 foreach my $volid (@$newvollist) {
3244 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3248 PVE
::Firewall
::remove_vmfw_conf
($newid);
3250 unlink $conffile; # avoid races -> last thing before die
3252 die "clone failed: $err";
3258 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3260 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3263 # Aquire exclusive lock lock for $newid
3264 my $lock_target_vm = sub {
3265 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3268 # exclusive lock if VM is running - else shared lock is enough;
3270 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3272 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3276 __PACKAGE__-
>register_method({
3277 name
=> 'move_vm_disk',
3278 path
=> '{vmid}/move_disk',
3282 description
=> "Move volume to different storage.",
3284 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3286 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3287 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3291 additionalProperties
=> 0,
3293 node
=> get_standard_option
('pve-node'),
3294 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3297 description
=> "The disk you want to move.",
3298 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3300 storage
=> get_standard_option
('pve-storage-id', {
3301 description
=> "Target storage.",
3302 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3306 description
=> "Target Format.",
3307 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3312 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3318 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3323 description
=> "Override I/O bandwidth limit (in KiB/s).",
3327 default => 'move limit from datacenter or storage config',
3333 description
=> "the task ID.",
3338 my $rpcenv = PVE
::RPCEnvironment
::get
();
3339 my $authuser = $rpcenv->get_user();
3341 my $node = extract_param
($param, 'node');
3342 my $vmid = extract_param
($param, 'vmid');
3343 my $digest = extract_param
($param, 'digest');
3344 my $disk = extract_param
($param, 'disk');
3345 my $storeid = extract_param
($param, 'storage');
3346 my $format = extract_param
($param, 'format');
3348 my $storecfg = PVE
::Storage
::config
();
3350 my $updatefn = sub {
3351 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3352 PVE
::QemuConfig-
>check_lock($conf);
3354 die "VM config checksum missmatch (file change by other user?)\n"
3355 if $digest && $digest ne $conf->{digest
};
3357 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3359 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3361 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3362 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3364 my $old_volid = $drive->{file
};
3366 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3367 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3371 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3372 (!$format || !$oldfmt || $oldfmt eq $format);
3374 # this only checks snapshots because $disk is passed!
3375 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3376 die "you can't move a disk with snapshots and delete the source\n"
3377 if $snapshotted && $param->{delete};
3379 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3381 my $running = PVE
::QemuServer
::check_running
($vmid);
3383 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3386 my $newvollist = [];
3392 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3394 warn "moving disk with snapshots, snapshots will not be moved!\n"
3397 my $bwlimit = extract_param
($param, 'bwlimit');
3398 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3400 my $newdrive = PVE
::QemuServer
::clone_disk
(
3418 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3420 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3422 # convert moved disk to base if part of template
3423 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3424 if PVE
::QemuConfig-
>is_template($conf);
3426 PVE
::QemuConfig-
>write_config($vmid, $conf);
3428 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3429 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3430 eval { mon_cmd
($vmid, "guest-fstrim") };
3434 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3435 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3441 foreach my $volid (@$newvollist) {
3442 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3445 die "storage migration failed: $err";
3448 if ($param->{delete}) {
3450 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3451 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3457 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3460 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3463 my $check_vm_disks_local = sub {
3464 my ($storecfg, $vmconf, $vmid) = @_;
3466 my $local_disks = {};
3468 # add some more information to the disks e.g. cdrom
3469 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3470 my ($volid, $attr) = @_;
3472 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3474 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3475 return if $scfg->{shared
};
3477 # The shared attr here is just a special case where the vdisk
3478 # is marked as shared manually
3479 return if $attr->{shared
};
3480 return if $attr->{cdrom
} and $volid eq "none";
3482 if (exists $local_disks->{$volid}) {
3483 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3485 $local_disks->{$volid} = $attr;
3486 # ensure volid is present in case it's needed
3487 $local_disks->{$volid}->{volid
} = $volid;
3491 return $local_disks;
3494 __PACKAGE__-
>register_method({
3495 name
=> 'migrate_vm_precondition',
3496 path
=> '{vmid}/migrate',
3500 description
=> "Get preconditions for migration.",
3502 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3505 additionalProperties
=> 0,
3507 node
=> get_standard_option
('pve-node'),
3508 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3509 target
=> get_standard_option
('pve-node', {
3510 description
=> "Target node.",
3511 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3519 running
=> { type
=> 'boolean' },
3523 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3525 not_allowed_nodes
=> {
3528 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3532 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3534 local_resources
=> {
3536 description
=> "List local resources e.g. pci, usb"
3543 my $rpcenv = PVE
::RPCEnvironment
::get
();
3545 my $authuser = $rpcenv->get_user();
3547 PVE
::Cluster
::check_cfs_quorum
();
3551 my $vmid = extract_param
($param, 'vmid');
3552 my $target = extract_param
($param, 'target');
3553 my $localnode = PVE
::INotify
::nodename
();
3557 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3558 my $storecfg = PVE
::Storage
::config
();
3561 # try to detect errors early
3562 PVE
::QemuConfig-
>check_lock($vmconf);
3564 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3566 # if vm is not running, return target nodes where local storage is available
3567 # for offline migration
3568 if (!$res->{running
}) {
3569 $res->{allowed_nodes
} = [];
3570 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3571 delete $checked_nodes->{$localnode};
3573 foreach my $node (keys %$checked_nodes) {
3574 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3575 push @{$res->{allowed_nodes
}}, $node;
3579 $res->{not_allowed_nodes
} = $checked_nodes;
3583 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3584 $res->{local_disks
} = [ values %$local_disks ];;
3586 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3588 $res->{local_resources
} = $local_resources;
3595 __PACKAGE__-
>register_method({
3596 name
=> 'migrate_vm',
3597 path
=> '{vmid}/migrate',
3601 description
=> "Migrate virtual machine. Creates a new migration task.",
3603 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3606 additionalProperties
=> 0,
3608 node
=> get_standard_option
('pve-node'),
3609 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3610 target
=> get_standard_option
('pve-node', {
3611 description
=> "Target node.",
3612 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3616 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3621 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3626 enum
=> ['secure', 'insecure'],
3627 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3630 migration_network
=> {
3631 type
=> 'string', format
=> 'CIDR',
3632 description
=> "CIDR of the (sub) network that is used for migration.",
3635 "with-local-disks" => {
3637 description
=> "Enable live storage migration for local disk",
3640 targetstorage
=> get_standard_option
('pve-targetstorage', {
3641 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3644 description
=> "Override I/O bandwidth limit (in KiB/s).",
3648 default => 'migrate limit from datacenter or storage config',
3654 description
=> "the task ID.",
3659 my $rpcenv = PVE
::RPCEnvironment
::get
();
3660 my $authuser = $rpcenv->get_user();
3662 my $target = extract_param
($param, 'target');
3664 my $localnode = PVE
::INotify
::nodename
();
3665 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3667 PVE
::Cluster
::check_cfs_quorum
();
3669 PVE
::Cluster
::check_node_exists
($target);
3671 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3673 my $vmid = extract_param
($param, 'vmid');
3675 raise_param_exc
({ force
=> "Only root may use this option." })
3676 if $param->{force
} && $authuser ne 'root@pam';
3678 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3679 if $param->{migration_type
} && $authuser ne 'root@pam';
3681 # allow root only until better network permissions are available
3682 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3683 if $param->{migration_network
} && $authuser ne 'root@pam';
3686 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3688 # try to detect errors early
3690 PVE
::QemuConfig-
>check_lock($conf);
3692 if (PVE
::QemuServer
::check_running
($vmid)) {
3693 die "can't migrate running VM without --online\n" if !$param->{online
};
3695 my $repl_conf = PVE
::ReplicationConfig-
>new();
3696 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3697 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3698 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3699 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3700 "target. Use 'force' to override.\n";
3703 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3704 $param->{online
} = 0;
3707 my $storecfg = PVE
::Storage
::config
();
3709 if (my $targetstorage = $param->{targetstorage
}) {
3710 my $check_storage = sub {
3711 my ($target_sid) = @_;
3712 PVE
::Storage
::storage_check_enabled
($storecfg, $target_sid, $target);
3713 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3714 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3715 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3716 if !$scfg->{content
}->{images
};
3719 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3720 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3723 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3724 if !defined($storagemap->{identity
});
3726 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3727 $check_storage->($target_sid);
3730 $check_storage->($storagemap->{default})
3731 if $storagemap->{default};
3733 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3734 if $storagemap->{identity
};
3736 $param->{storagemap
} = $storagemap;
3738 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3741 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3746 print "Requesting HA migration for VM $vmid to node $target\n";
3748 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3749 PVE
::Tools
::run_command
($cmd);
3753 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3758 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3762 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3765 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3770 __PACKAGE__-
>register_method({
3772 path
=> '{vmid}/monitor',
3776 description
=> "Execute Qemu monitor commands.",
3778 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3779 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3782 additionalProperties
=> 0,
3784 node
=> get_standard_option
('pve-node'),
3785 vmid
=> get_standard_option
('pve-vmid'),
3788 description
=> "The monitor command.",
3792 returns
=> { type
=> 'string'},
3796 my $rpcenv = PVE
::RPCEnvironment
::get
();
3797 my $authuser = $rpcenv->get_user();
3800 my $command = shift;
3801 return $command =~ m/^\s*info(\s+|$)/
3802 || $command =~ m/^\s*help\s*$/;
3805 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3806 if !&$is_ro($param->{command
});
3808 my $vmid = $param->{vmid
};
3810 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3814 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3816 $res = "ERROR: $@" if $@;
3821 __PACKAGE__-
>register_method({
3822 name
=> 'resize_vm',
3823 path
=> '{vmid}/resize',
3827 description
=> "Extend volume size.",
3829 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3832 additionalProperties
=> 0,
3834 node
=> get_standard_option
('pve-node'),
3835 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3836 skiplock
=> get_standard_option
('skiplock'),
3839 description
=> "The disk you want to resize.",
3840 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3844 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3845 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.",
3849 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3855 returns
=> { type
=> 'null'},
3859 my $rpcenv = PVE
::RPCEnvironment
::get
();
3861 my $authuser = $rpcenv->get_user();
3863 my $node = extract_param
($param, 'node');
3865 my $vmid = extract_param
($param, 'vmid');
3867 my $digest = extract_param
($param, 'digest');
3869 my $disk = extract_param
($param, 'disk');
3871 my $sizestr = extract_param
($param, 'size');
3873 my $skiplock = extract_param
($param, 'skiplock');
3874 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3875 if $skiplock && $authuser ne 'root@pam';
3877 my $storecfg = PVE
::Storage
::config
();
3879 my $updatefn = sub {
3881 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3883 die "checksum missmatch (file change by other user?)\n"
3884 if $digest && $digest ne $conf->{digest
};
3885 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3887 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3889 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3891 my (undef, undef, undef, undef, undef, undef, $format) =
3892 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3894 die "can't resize volume: $disk if snapshot exists\n"
3895 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3897 my $volid = $drive->{file
};
3899 die "disk '$disk' has no associated volume\n" if !$volid;
3901 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3903 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3905 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3907 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3908 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3910 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3912 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3913 my ($ext, $newsize, $unit) = ($1, $2, $4);
3916 $newsize = $newsize * 1024;
3917 } elsif ($unit eq 'M') {
3918 $newsize = $newsize * 1024 * 1024;
3919 } elsif ($unit eq 'G') {
3920 $newsize = $newsize * 1024 * 1024 * 1024;
3921 } elsif ($unit eq 'T') {
3922 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3925 $newsize += $size if $ext;
3926 $newsize = int($newsize);
3928 die "shrinking disks is not supported\n" if $newsize < $size;
3930 return if $size == $newsize;
3932 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3934 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3936 $drive->{size
} = $newsize;
3937 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3939 PVE
::QemuConfig-
>write_config($vmid, $conf);
3942 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3946 __PACKAGE__-
>register_method({
3947 name
=> 'snapshot_list',
3948 path
=> '{vmid}/snapshot',
3950 description
=> "List all snapshots.",
3952 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3955 protected
=> 1, # qemu pid files are only readable by root
3957 additionalProperties
=> 0,
3959 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3960 node
=> get_standard_option
('pve-node'),
3969 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3973 description
=> "Snapshot includes RAM.",
3978 description
=> "Snapshot description.",
3982 description
=> "Snapshot creation time",
3984 renderer
=> 'timestamp',
3988 description
=> "Parent snapshot identifier.",
3994 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3999 my $vmid = $param->{vmid
};
4001 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4002 my $snaphash = $conf->{snapshots
} || {};
4006 foreach my $name (keys %$snaphash) {
4007 my $d = $snaphash->{$name};
4010 snaptime
=> $d->{snaptime
} || 0,
4011 vmstate
=> $d->{vmstate
} ?
1 : 0,
4012 description
=> $d->{description
} || '',
4014 $item->{parent
} = $d->{parent
} if $d->{parent
};
4015 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4019 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4022 digest
=> $conf->{digest
},
4023 running
=> $running,
4024 description
=> "You are here!",
4026 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4028 push @$res, $current;
4033 __PACKAGE__-
>register_method({
4035 path
=> '{vmid}/snapshot',
4039 description
=> "Snapshot a VM.",
4041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4044 additionalProperties
=> 0,
4046 node
=> get_standard_option
('pve-node'),
4047 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4048 snapname
=> get_standard_option
('pve-snapshot-name'),
4052 description
=> "Save the vmstate",
4057 description
=> "A textual description or comment.",
4063 description
=> "the task ID.",
4068 my $rpcenv = PVE
::RPCEnvironment
::get
();
4070 my $authuser = $rpcenv->get_user();
4072 my $node = extract_param
($param, 'node');
4074 my $vmid = extract_param
($param, 'vmid');
4076 my $snapname = extract_param
($param, 'snapname');
4078 die "unable to use snapshot name 'current' (reserved name)\n"
4079 if $snapname eq 'current';
4081 die "unable to use snapshot name 'pending' (reserved name)\n"
4082 if lc($snapname) eq 'pending';
4085 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4086 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4087 $param->{description
});
4090 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4093 __PACKAGE__-
>register_method({
4094 name
=> 'snapshot_cmd_idx',
4095 path
=> '{vmid}/snapshot/{snapname}',
4102 additionalProperties
=> 0,
4104 vmid
=> get_standard_option
('pve-vmid'),
4105 node
=> get_standard_option
('pve-node'),
4106 snapname
=> get_standard_option
('pve-snapshot-name'),
4115 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4122 push @$res, { cmd
=> 'rollback' };
4123 push @$res, { cmd
=> 'config' };
4128 __PACKAGE__-
>register_method({
4129 name
=> 'update_snapshot_config',
4130 path
=> '{vmid}/snapshot/{snapname}/config',
4134 description
=> "Update snapshot metadata.",
4136 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4139 additionalProperties
=> 0,
4141 node
=> get_standard_option
('pve-node'),
4142 vmid
=> get_standard_option
('pve-vmid'),
4143 snapname
=> get_standard_option
('pve-snapshot-name'),
4147 description
=> "A textual description or comment.",
4151 returns
=> { type
=> 'null' },
4155 my $rpcenv = PVE
::RPCEnvironment
::get
();
4157 my $authuser = $rpcenv->get_user();
4159 my $vmid = extract_param
($param, 'vmid');
4161 my $snapname = extract_param
($param, 'snapname');
4163 return if !defined($param->{description
});
4165 my $updatefn = sub {
4167 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4169 PVE
::QemuConfig-
>check_lock($conf);
4171 my $snap = $conf->{snapshots
}->{$snapname};
4173 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4175 $snap->{description
} = $param->{description
} if defined($param->{description
});
4177 PVE
::QemuConfig-
>write_config($vmid, $conf);
4180 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4185 __PACKAGE__-
>register_method({
4186 name
=> 'get_snapshot_config',
4187 path
=> '{vmid}/snapshot/{snapname}/config',
4190 description
=> "Get snapshot configuration",
4192 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4195 additionalProperties
=> 0,
4197 node
=> get_standard_option
('pve-node'),
4198 vmid
=> get_standard_option
('pve-vmid'),
4199 snapname
=> get_standard_option
('pve-snapshot-name'),
4202 returns
=> { type
=> "object" },
4206 my $rpcenv = PVE
::RPCEnvironment
::get
();
4208 my $authuser = $rpcenv->get_user();
4210 my $vmid = extract_param
($param, 'vmid');
4212 my $snapname = extract_param
($param, 'snapname');
4214 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4216 my $snap = $conf->{snapshots
}->{$snapname};
4218 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4223 __PACKAGE__-
>register_method({
4225 path
=> '{vmid}/snapshot/{snapname}/rollback',
4229 description
=> "Rollback VM state to specified snapshot.",
4231 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4234 additionalProperties
=> 0,
4236 node
=> get_standard_option
('pve-node'),
4237 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4238 snapname
=> get_standard_option
('pve-snapshot-name'),
4243 description
=> "the task ID.",
4248 my $rpcenv = PVE
::RPCEnvironment
::get
();
4250 my $authuser = $rpcenv->get_user();
4252 my $node = extract_param
($param, 'node');
4254 my $vmid = extract_param
($param, 'vmid');
4256 my $snapname = extract_param
($param, 'snapname');
4259 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4260 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4264 # hold migration lock, this makes sure that nobody create replication snapshots
4265 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4268 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4271 __PACKAGE__-
>register_method({
4272 name
=> 'delsnapshot',
4273 path
=> '{vmid}/snapshot/{snapname}',
4277 description
=> "Delete a VM snapshot.",
4279 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4282 additionalProperties
=> 0,
4284 node
=> get_standard_option
('pve-node'),
4285 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4286 snapname
=> get_standard_option
('pve-snapshot-name'),
4290 description
=> "For removal from config file, even if removing disk snapshots fails.",
4296 description
=> "the task ID.",
4301 my $rpcenv = PVE
::RPCEnvironment
::get
();
4303 my $authuser = $rpcenv->get_user();
4305 my $node = extract_param
($param, 'node');
4307 my $vmid = extract_param
($param, 'vmid');
4309 my $snapname = extract_param
($param, 'snapname');
4312 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4313 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4316 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4319 __PACKAGE__-
>register_method({
4321 path
=> '{vmid}/template',
4325 description
=> "Create a Template.",
4327 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4328 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4331 additionalProperties
=> 0,
4333 node
=> get_standard_option
('pve-node'),
4334 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4338 description
=> "If you want to convert only 1 disk to base image.",
4339 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4346 description
=> "the task ID.",
4351 my $rpcenv = PVE
::RPCEnvironment
::get
();
4353 my $authuser = $rpcenv->get_user();
4355 my $node = extract_param
($param, 'node');
4357 my $vmid = extract_param
($param, 'vmid');
4359 my $disk = extract_param
($param, 'disk');
4361 my $load_and_check = sub {
4362 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4364 PVE
::QemuConfig-
>check_lock($conf);
4366 die "unable to create template, because VM contains snapshots\n"
4367 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4369 die "you can't convert a template to a template\n"
4370 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4372 die "you can't convert a VM to template if VM is running\n"
4373 if PVE
::QemuServer
::check_running
($vmid);
4378 $load_and_check->();
4381 PVE
::QemuConfig-
>lock_config($vmid, sub {
4382 my $conf = $load_and_check->();
4384 $conf->{template
} = 1;
4385 PVE
::QemuConfig-
>write_config($vmid, $conf);
4387 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4391 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4394 __PACKAGE__-
>register_method({
4395 name
=> 'cloudinit_generated_config_dump',
4396 path
=> '{vmid}/cloudinit/dump',
4399 description
=> "Get automatically generated cloudinit config.",
4401 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4404 additionalProperties
=> 0,
4406 node
=> get_standard_option
('pve-node'),
4407 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4409 description
=> 'Config type.',
4411 enum
=> ['user', 'network', 'meta'],
4421 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4423 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});