1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::RPCEnvironment
;
29 use PVE
::AccessControl
;
33 use PVE
::API2
::Firewall
::VM
;
34 use PVE
::API2
::Qemu
::Agent
;
35 use PVE
::VZDump
::Plugin
;
36 use PVE
::DataCenterConfig
;
40 if (!$ENV{PVE_GENERATING_DOCS
}) {
41 require PVE
::HA
::Env
::PVE2
;
42 import PVE
::HA
::Env
::PVE2
;
43 require PVE
::HA
::Config
;
44 import PVE
::HA
::Config
;
48 use Data
::Dumper
; # fixme: remove
50 use base
qw(PVE::RESTHandler);
52 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
54 my $resolve_cdrom_alias = sub {
57 if (my $value = $param->{cdrom
}) {
58 $value .= ",media=cdrom" if $value !~ m/media=/;
59 $param->{ide2
} = $value;
60 delete $param->{cdrom
};
64 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
65 my $check_storage_access = sub {
66 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
68 PVE
::QemuConfig-
>foreach_volume($settings, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
74 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
76 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
78 } elsif ($isCDROM && ($volid eq 'cdrom')) {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
81 my ($storeid, $size) = ($2 || $default_storage, $3);
82 die "no storage ID specified (and no default storage)\n" if !$storeid;
83 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
85 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
86 if !$scfg->{content
}->{images
};
88 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
92 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
93 if defined($settings->{vmstatestorage
});
96 my $check_storage_access_clone = sub {
97 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
101 PVE
::QemuConfig-
>foreach_volume($conf, sub {
102 my ($ds, $drive) = @_;
104 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
106 my $volid = $drive->{file
};
108 return if !$volid || $volid eq 'none';
111 if ($volid eq 'cdrom') {
112 $rpcenv->check($authuser, "/", ['Sys.Console']);
114 # we simply allow access
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
121 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
122 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
123 $sharedvm = 0 if !$scfg->{shared
};
125 $sid = $storage if $storage;
126 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
130 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
131 if defined($conf->{vmstatestorage
});
136 # Note: $pool is only needed when creating a VM, because pool permissions
137 # are automatically inherited if VM already exists inside a pool.
138 my $create_disks = sub {
139 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
146 my ($ds, $disk) = @_;
148 my $volid = $disk->{file
};
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
151 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
152 delete $disk->{size
};
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
154 } elsif (defined($volname) && $volname eq 'cloudinit') {
155 $storeid = $storeid // $default_storage;
156 die "no storage ID specified (and no default storage)\n" if !$storeid;
157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
158 my $name = "vm-$vmid-cloudinit";
162 $fmt = $disk->{format
} // "qcow2";
165 $fmt = $disk->{format
} // "raw";
168 # Initial disk created with 4 MB and aligned to 4MB on regeneration
169 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
170 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
171 $disk->{file
} = $volid;
172 $disk->{media
} = 'cdrom';
173 push @$vollist, $volid;
174 delete $disk->{format
}; # no longer needed
175 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
176 } elsif ($volid =~ $NEW_DISK_RE) {
177 my ($storeid, $size) = ($2 || $default_storage, $3);
178 die "no storage ID specified (and no default storage)\n" if !$storeid;
179 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
180 my $fmt = $disk->{format
} || $defformat;
182 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
185 if ($ds eq 'efidisk0') {
186 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
188 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
190 push @$vollist, $volid;
191 $disk->{file
} = $volid;
192 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
193 delete $disk->{format
}; # no longer needed
194 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
197 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
199 my $volid_is_new = 1;
202 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
203 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
208 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
210 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
212 die "volume $volid does not exist\n" if !$size;
214 $disk->{size
} = $size;
217 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
221 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
223 # free allocated images on error
225 syslog
('err', "VM $vmid creating disks failed");
226 foreach my $volid (@$vollist) {
227 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
233 # modify vm config if everything went well
234 foreach my $ds (keys %$res) {
235 $conf->{$ds} = $res->{$ds};
241 my $check_cpu_model_access = sub {
242 my ($rpcenv, $authuser, $new, $existing) = @_;
244 return if !defined($new->{cpu
});
246 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
247 return if !$cpu || !$cpu->{cputype
}; # always allow default
248 my $cputype = $cpu->{cputype
};
250 if ($existing && $existing->{cpu
}) {
251 # changing only other settings doesn't require permissions for CPU model
252 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
253 return if $existingCpu->{cputype
} eq $cputype;
256 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
257 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
272 my $memoryoptions = {
278 my $hwtypeoptions = {
291 my $generaloptions = {
298 'migrate_downtime' => 1,
299 'migrate_speed' => 1,
312 my $vmpoweroptions = {
319 'vmstatestorage' => 1,
322 my $cloudinitoptions = {
332 my $check_vm_create_serial_perm = sub {
333 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
335 return 1 if $authuser eq 'root@pam';
337 foreach my $opt (keys %{$param}) {
338 next if $opt !~ m/^serial\d+$/;
340 if ($param->{$opt} eq 'socket') {
341 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
343 die "only root can set '$opt' config for real devices\n";
350 my $check_vm_create_usb_perm = sub {
351 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
353 return 1 if $authuser eq 'root@pam';
355 foreach my $opt (keys %{$param}) {
356 next if $opt !~ m/^usb\d+$/;
358 if ($param->{$opt} =~ m/spice/) {
359 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
361 die "only root can set '$opt' config for real devices\n";
368 my $check_vm_modify_config_perm = sub {
369 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
371 return 1 if $authuser eq 'root@pam';
373 foreach my $opt (@$key_list) {
374 # some checks (e.g., disk, serial port, usb) need to be done somewhere
375 # else, as there the permission can be value dependend
376 next if PVE
::QemuServer
::is_valid_drivename
($opt);
377 next if $opt eq 'cdrom';
378 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
381 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
382 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
383 } elsif ($memoryoptions->{$opt}) {
384 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
385 } elsif ($hwtypeoptions->{$opt}) {
386 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
387 } elsif ($generaloptions->{$opt}) {
388 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
389 # special case for startup since it changes host behaviour
390 if ($opt eq 'startup') {
391 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
393 } elsif ($vmpoweroptions->{$opt}) {
394 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
395 } elsif ($diskoptions->{$opt}) {
396 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
397 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
398 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
399 } elsif ($cloudinitoptions->{$opt}) {
400 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
401 } elsif ($opt eq 'vmstate') {
402 # the user needs Disk and PowerMgmt privileges to change the vmstate
403 # also needs privileges on the storage, that will be checked later
404 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
406 # catches hostpci\d+, args, lock, etc.
407 # new options will be checked here
408 die "only root can set '$opt' config\n";
415 __PACKAGE__-
>register_method({
419 description
=> "Virtual machine index (per node).",
421 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
425 protected
=> 1, # qemu pid files are only readable by root
427 additionalProperties
=> 0,
429 node
=> get_standard_option
('pve-node'),
433 description
=> "Determine the full status of active VMs.",
441 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
443 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
448 my $rpcenv = PVE
::RPCEnvironment
::get
();
449 my $authuser = $rpcenv->get_user();
451 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
454 foreach my $vmid (keys %$vmstatus) {
455 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
457 my $data = $vmstatus->{$vmid};
464 my $parse_restore_archive = sub {
465 my ($storecfg, $archive) = @_;
467 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
469 if (defined($archive_storeid)) {
470 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
471 if ($scfg->{type
} eq 'pbs') {
478 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
486 __PACKAGE__-
>register_method({
490 description
=> "Create or restore a virtual machine.",
492 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
493 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
494 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
495 user
=> 'all', # check inside
500 additionalProperties
=> 0,
501 properties
=> PVE
::QemuServer
::json_config_properties
(
503 node
=> get_standard_option
('pve-node'),
504 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
506 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
510 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
512 storage
=> get_standard_option
('pve-storage-id', {
513 description
=> "Default storage.",
515 completion
=> \
&PVE
::QemuServer
::complete_storage
,
520 description
=> "Allow to overwrite existing VM.",
521 requires
=> 'archive',
526 description
=> "Assign a unique random ethernet address.",
527 requires
=> 'archive',
532 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
533 requires
=> 'archive',
537 type
=> 'string', format
=> 'pve-poolid',
538 description
=> "Add the VM to the specified pool.",
541 description
=> "Override I/O bandwidth limit (in KiB/s).",
545 default => 'restore limit from datacenter or storage config',
551 description
=> "Start VM after it was created successfully.",
561 my $rpcenv = PVE
::RPCEnvironment
::get
();
562 my $authuser = $rpcenv->get_user();
564 my $node = extract_param
($param, 'node');
565 my $vmid = extract_param
($param, 'vmid');
567 my $archive = extract_param
($param, 'archive');
568 my $is_restore = !!$archive;
570 my $bwlimit = extract_param
($param, 'bwlimit');
571 my $force = extract_param
($param, 'force');
572 my $pool = extract_param
($param, 'pool');
573 my $start_after_create = extract_param
($param, 'start');
574 my $storage = extract_param
($param, 'storage');
575 my $unique = extract_param
($param, 'unique');
576 my $live_restore = extract_param
($param, 'live-restore');
578 if (defined(my $ssh_keys = $param->{sshkeys
})) {
579 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
580 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
583 PVE
::Cluster
::check_cfs_quorum
();
585 my $filename = PVE
::QemuConfig-
>config_file($vmid);
586 my $storecfg = PVE
::Storage
::config
();
588 if (defined($pool)) {
589 $rpcenv->check_pool_exist($pool);
592 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
593 if defined($storage);
595 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
597 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
599 } elsif ($archive && $force && (-f
$filename) &&
600 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
601 # OK: user has VM.Backup permissions, and want to restore an existing VM
607 &$resolve_cdrom_alias($param);
609 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
611 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
613 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
614 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
616 &$check_cpu_model_access($rpcenv, $authuser, $param);
618 foreach my $opt (keys %$param) {
619 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
620 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
621 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
623 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
624 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
628 PVE
::QemuServer
::add_random_macs
($param);
630 my $keystr = join(' ', keys %$param);
631 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
633 if ($archive eq '-') {
634 die "pipe requires cli environment\n"
635 if $rpcenv->{type
} ne 'cli';
636 $archive = { type
=> 'pipe' };
638 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
640 $archive = $parse_restore_archive->($storecfg, $archive);
644 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
646 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
647 die "$emsg $@" if $@;
649 my $restorefn = sub {
650 my $conf = PVE
::QemuConfig-
>load_config($vmid);
652 PVE
::QemuConfig-
>check_protection($conf, $emsg);
654 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
657 my $restore_options = {
662 live
=> $live_restore,
664 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
665 die "live-restore is only compatible with PBS\n" if $live_restore;
666 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
667 } elsif ($archive->{type
} eq 'pbs') {
668 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
670 die "unknown backup archive type\n";
672 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
673 # Convert restored VM to template if backup was VM template
674 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
675 warn "Convert to template.\n";
676 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
681 # ensure no old replication state are exists
682 PVE
::ReplicationState
::delete_guest_states
($vmid);
684 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
686 if ($start_after_create && !$live_restore) {
687 print "Execute autostart\n";
688 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
694 # ensure no old replication state are exists
695 PVE
::ReplicationState
::delete_guest_states
($vmid);
699 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
703 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
705 if (!$conf->{boot
}) {
706 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
707 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
710 # auto generate uuid if user did not specify smbios1 option
711 if (!$conf->{smbios1
}) {
712 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
715 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
716 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
719 my $machine = $conf->{machine
};
720 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
721 # always pin Windows' machine version on create, they get to easily confused
722 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
723 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
727 PVE
::QemuConfig-
>write_config($vmid, $conf);
733 foreach my $volid (@$vollist) {
734 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
740 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
743 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
745 if ($start_after_create) {
746 print "Execute autostart\n";
747 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
752 my ($code, $worker_name);
754 $worker_name = 'qmrestore';
756 eval { $restorefn->() };
758 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
764 $worker_name = 'qmcreate';
766 eval { $createfn->() };
769 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
770 unlink($conffile) or die "failed to remove config file: $!\n";
778 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
781 __PACKAGE__-
>register_method({
786 description
=> "Directory index",
791 additionalProperties
=> 0,
793 node
=> get_standard_option
('pve-node'),
794 vmid
=> get_standard_option
('pve-vmid'),
802 subdir
=> { type
=> 'string' },
805 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
811 { subdir
=> 'config' },
812 { subdir
=> 'pending' },
813 { subdir
=> 'status' },
814 { subdir
=> 'unlink' },
815 { subdir
=> 'vncproxy' },
816 { subdir
=> 'termproxy' },
817 { subdir
=> 'migrate' },
818 { subdir
=> 'resize' },
819 { subdir
=> 'move' },
821 { subdir
=> 'rrddata' },
822 { subdir
=> 'monitor' },
823 { subdir
=> 'agent' },
824 { subdir
=> 'snapshot' },
825 { subdir
=> 'spiceproxy' },
826 { subdir
=> 'sendkey' },
827 { subdir
=> 'firewall' },
833 __PACKAGE__-
>register_method ({
834 subclass
=> "PVE::API2::Firewall::VM",
835 path
=> '{vmid}/firewall',
838 __PACKAGE__-
>register_method ({
839 subclass
=> "PVE::API2::Qemu::Agent",
840 path
=> '{vmid}/agent',
843 __PACKAGE__-
>register_method({
845 path
=> '{vmid}/rrd',
847 protected
=> 1, # fixme: can we avoid that?
849 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
851 description
=> "Read VM RRD statistics (returns PNG)",
853 additionalProperties
=> 0,
855 node
=> get_standard_option
('pve-node'),
856 vmid
=> get_standard_option
('pve-vmid'),
858 description
=> "Specify the time frame you are interested in.",
860 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
863 description
=> "The list of datasources you want to display.",
864 type
=> 'string', format
=> 'pve-configid-list',
867 description
=> "The RRD consolidation function",
869 enum
=> [ 'AVERAGE', 'MAX' ],
877 filename
=> { type
=> 'string' },
883 return PVE
::RRD
::create_rrd_graph
(
884 "pve2-vm/$param->{vmid}", $param->{timeframe
},
885 $param->{ds
}, $param->{cf
});
889 __PACKAGE__-
>register_method({
891 path
=> '{vmid}/rrddata',
893 protected
=> 1, # fixme: can we avoid that?
895 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
897 description
=> "Read VM RRD statistics",
899 additionalProperties
=> 0,
901 node
=> get_standard_option
('pve-node'),
902 vmid
=> get_standard_option
('pve-vmid'),
904 description
=> "Specify the time frame you are interested in.",
906 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
909 description
=> "The RRD consolidation function",
911 enum
=> [ 'AVERAGE', 'MAX' ],
926 return PVE
::RRD
::create_rrd_data
(
927 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
931 __PACKAGE__-
>register_method({
933 path
=> '{vmid}/config',
936 description
=> "Get the virtual machine configuration with pending configuration " .
937 "changes applied. Set the 'current' parameter to get the current configuration instead.",
939 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
942 additionalProperties
=> 0,
944 node
=> get_standard_option
('pve-node'),
945 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
947 description
=> "Get current values (instead of pending values).",
952 snapshot
=> get_standard_option
('pve-snapshot-name', {
953 description
=> "Fetch config values from given snapshot.",
956 my ($cmd, $pname, $cur, $args) = @_;
957 PVE
::QemuConfig-
>snapshot_list($args->[0]);
963 description
=> "The VM configuration.",
965 properties
=> PVE
::QemuServer
::json_config_properties
({
968 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
975 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
976 current
=> "cannot use 'snapshot' parameter with 'current'"})
977 if ($param->{snapshot
} && $param->{current
});
980 if ($param->{snapshot
}) {
981 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
983 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
985 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
990 __PACKAGE__-
>register_method({
991 name
=> 'vm_pending',
992 path
=> '{vmid}/pending',
995 description
=> "Get the virtual machine configuration with both current and pending values.",
997 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1000 additionalProperties
=> 0,
1002 node
=> get_standard_option
('pve-node'),
1003 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1012 description
=> "Configuration option name.",
1016 description
=> "Current value.",
1021 description
=> "Pending value.",
1026 description
=> "Indicates a pending delete request if present and not 0. " .
1027 "The value 2 indicates a force-delete request.",
1039 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1041 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1043 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1044 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1046 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1049 # POST/PUT {vmid}/config implementation
1051 # The original API used PUT (idempotent) an we assumed that all operations
1052 # are fast. But it turned out that almost any configuration change can
1053 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1054 # time to complete and have side effects (not idempotent).
1056 # The new implementation uses POST and forks a worker process. We added
1057 # a new option 'background_delay'. If specified we wait up to
1058 # 'background_delay' second for the worker task to complete. It returns null
1059 # if the task is finished within that time, else we return the UPID.
1061 my $update_vm_api = sub {
1062 my ($param, $sync) = @_;
1064 my $rpcenv = PVE
::RPCEnvironment
::get
();
1066 my $authuser = $rpcenv->get_user();
1068 my $node = extract_param
($param, 'node');
1070 my $vmid = extract_param
($param, 'vmid');
1072 my $digest = extract_param
($param, 'digest');
1074 my $background_delay = extract_param
($param, 'background_delay');
1076 if (defined(my $cipassword = $param->{cipassword
})) {
1077 # Same logic as in cloud-init (but with the regex fixed...)
1078 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1079 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1082 my @paramarr = (); # used for log message
1083 foreach my $key (sort keys %$param) {
1084 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1085 push @paramarr, "-$key", $value;
1088 my $skiplock = extract_param
($param, 'skiplock');
1089 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1090 if $skiplock && $authuser ne 'root@pam';
1092 my $delete_str = extract_param
($param, 'delete');
1094 my $revert_str = extract_param
($param, 'revert');
1096 my $force = extract_param
($param, 'force');
1098 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1099 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1100 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1103 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1105 my $storecfg = PVE
::Storage
::config
();
1107 my $defaults = PVE
::QemuServer
::load_defaults
();
1109 &$resolve_cdrom_alias($param);
1111 # now try to verify all parameters
1114 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1115 if (!PVE
::QemuServer
::option_exists
($opt)) {
1116 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1119 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1120 "-revert $opt' at the same time" })
1121 if defined($param->{$opt});
1123 $revert->{$opt} = 1;
1127 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1128 $opt = 'ide2' if $opt eq 'cdrom';
1130 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1131 "-delete $opt' at the same time" })
1132 if defined($param->{$opt});
1134 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1135 "-revert $opt' at the same time" })
1138 if (!PVE
::QemuServer
::option_exists
($opt)) {
1139 raise_param_exc
({ delete => "unknown option '$opt'" });
1145 my $repl_conf = PVE
::ReplicationConfig-
>new();
1146 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1147 my $check_replication = sub {
1149 return if !$is_replicated;
1150 my $volid = $drive->{file
};
1151 return if !$volid || !($drive->{replicate
}//1);
1152 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1154 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1155 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1156 if !defined($storeid);
1158 return if defined($volname) && $volname eq 'cloudinit';
1161 if ($volid =~ $NEW_DISK_RE) {
1163 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1165 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1167 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1168 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1169 return if $scfg->{shared
};
1170 die "cannot add non-replicatable volume to a replicated VM\n";
1173 foreach my $opt (keys %$param) {
1174 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1175 # cleanup drive path
1176 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1177 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1178 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1179 $check_replication->($drive);
1180 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1181 } elsif ($opt =~ m/^net(\d+)$/) {
1183 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1184 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1185 } elsif ($opt eq 'vmgenid') {
1186 if ($param->{$opt} eq '1') {
1187 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1189 } elsif ($opt eq 'hookscript') {
1190 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1191 raise_param_exc
({ $opt => $@ }) if $@;
1195 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1197 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1199 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1201 my $updatefn = sub {
1203 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1205 die "checksum missmatch (file change by other user?)\n"
1206 if $digest && $digest ne $conf->{digest
};
1208 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1210 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1211 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1212 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1213 delete $conf->{lock}; # for check lock check, not written out
1214 push @delete, 'lock'; # this is the real deal to write it out
1216 push @delete, 'runningmachine' if $conf->{runningmachine
};
1217 push @delete, 'runningcpu' if $conf->{runningcpu
};
1220 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1222 foreach my $opt (keys %$revert) {
1223 if (defined($conf->{$opt})) {
1224 $param->{$opt} = $conf->{$opt};
1225 } elsif (defined($conf->{pending
}->{$opt})) {
1230 if ($param->{memory
} || defined($param->{balloon
})) {
1231 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1232 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1234 die "balloon value too large (must be smaller than assigned memory)\n"
1235 if $balloon && $balloon > $maxmem;
1238 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1242 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1244 # write updates to pending section
1246 my $modified = {}; # record what $option we modify
1249 if (my $boot = $conf->{boot
}) {
1250 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1251 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1253 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1255 foreach my $opt (@delete) {
1256 $modified->{$opt} = 1;
1257 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1259 # value of what we want to delete, independent if pending or not
1260 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1261 if (!defined($val)) {
1262 warn "cannot delete '$opt' - not set in current configuration!\n";
1263 $modified->{$opt} = 0;
1266 my $is_pending_val = defined($conf->{pending
}->{$opt});
1267 delete $conf->{pending
}->{$opt};
1269 # remove from bootorder if necessary
1270 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1271 @bootorder = grep {$_ ne $opt} @bootorder;
1272 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1273 $modified->{boot
} = 1;
1276 if ($opt =~ m/^unused/) {
1277 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1278 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1279 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1280 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1281 delete $conf->{$opt};
1282 PVE
::QemuConfig-
>write_config($vmid, $conf);
1284 } elsif ($opt eq 'vmstate') {
1285 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1286 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1287 delete $conf->{$opt};
1288 PVE
::QemuConfig-
>write_config($vmid, $conf);
1290 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1291 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1292 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1293 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1294 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1296 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1298 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1300 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1301 PVE
::QemuConfig-
>write_config($vmid, $conf);
1302 } elsif ($opt =~ m/^serial\d+$/) {
1303 if ($val eq 'socket') {
1304 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1305 } elsif ($authuser ne 'root@pam') {
1306 die "only root can delete '$opt' config for real devices\n";
1308 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1309 PVE
::QemuConfig-
>write_config($vmid, $conf);
1310 } elsif ($opt =~ m/^usb\d+$/) {
1311 if ($val =~ m/spice/) {
1312 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1313 } elsif ($authuser ne 'root@pam') {
1314 die "only root can delete '$opt' config for real devices\n";
1316 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1317 PVE
::QemuConfig-
>write_config($vmid, $conf);
1319 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1320 PVE
::QemuConfig-
>write_config($vmid, $conf);
1324 foreach my $opt (keys %$param) { # add/change
1325 $modified->{$opt} = 1;
1326 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1327 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1329 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1331 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1332 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1333 # FIXME: cloudinit: CDROM or Disk?
1334 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1335 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1337 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1339 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1340 if defined($conf->{pending
}->{$opt});
1342 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1343 } elsif ($opt =~ m/^serial\d+/) {
1344 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1345 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1346 } elsif ($authuser ne 'root@pam') {
1347 die "only root can modify '$opt' config for real devices\n";
1349 $conf->{pending
}->{$opt} = $param->{$opt};
1350 } elsif ($opt =~ m/^usb\d+/) {
1351 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1352 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1353 } elsif ($authuser ne 'root@pam') {
1354 die "only root can modify '$opt' config for real devices\n";
1356 $conf->{pending
}->{$opt} = $param->{$opt};
1358 $conf->{pending
}->{$opt} = $param->{$opt};
1360 if ($opt eq 'boot') {
1361 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1362 if ($new_bootcfg->{order
}) {
1363 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1364 for my $dev (@devs) {
1365 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1366 my $deleted = grep {$_ eq $dev} @delete;
1367 die "invalid bootorder: device '$dev' does not exist'\n"
1368 if !$exists || $deleted;
1371 # remove legacy boot order settings if new one set
1372 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1373 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1374 if $conf->{bootdisk
};
1378 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1379 PVE
::QemuConfig-
>write_config($vmid, $conf);
1382 # remove pending changes when nothing changed
1383 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1384 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1385 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1387 return if !scalar(keys %{$conf->{pending
}});
1389 my $running = PVE
::QemuServer
::check_running
($vmid);
1391 # apply pending changes
1393 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1397 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1399 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1401 raise_param_exc
($errors) if scalar(keys %$errors);
1410 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1412 if ($background_delay) {
1414 # Note: It would be better to do that in the Event based HTTPServer
1415 # to avoid blocking call to sleep.
1417 my $end_time = time() + $background_delay;
1419 my $task = PVE
::Tools
::upid_decode
($upid);
1422 while (time() < $end_time) {
1423 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1425 sleep(1); # this gets interrupted when child process ends
1429 my $status = PVE
::Tools
::upid_read_status
($upid);
1430 return if $status eq 'OK';
1439 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1442 my $vm_config_perm_list = [
1447 'VM.Config.Network',
1449 'VM.Config.Options',
1450 'VM.Config.Cloudinit',
1453 __PACKAGE__-
>register_method({
1454 name
=> 'update_vm_async',
1455 path
=> '{vmid}/config',
1459 description
=> "Set virtual machine options (asynchrounous API).",
1461 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1464 additionalProperties
=> 0,
1465 properties
=> PVE
::QemuServer
::json_config_properties
(
1467 node
=> get_standard_option
('pve-node'),
1468 vmid
=> get_standard_option
('pve-vmid'),
1469 skiplock
=> get_standard_option
('skiplock'),
1471 type
=> 'string', format
=> 'pve-configid-list',
1472 description
=> "A list of settings you want to delete.",
1476 type
=> 'string', format
=> 'pve-configid-list',
1477 description
=> "Revert a pending change.",
1482 description
=> $opt_force_description,
1484 requires
=> 'delete',
1488 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1492 background_delay
=> {
1494 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1505 code
=> $update_vm_api,
1508 __PACKAGE__-
>register_method({
1509 name
=> 'update_vm',
1510 path
=> '{vmid}/config',
1514 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1516 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1519 additionalProperties
=> 0,
1520 properties
=> PVE
::QemuServer
::json_config_properties
(
1522 node
=> get_standard_option
('pve-node'),
1523 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1524 skiplock
=> get_standard_option
('skiplock'),
1526 type
=> 'string', format
=> 'pve-configid-list',
1527 description
=> "A list of settings you want to delete.",
1531 type
=> 'string', format
=> 'pve-configid-list',
1532 description
=> "Revert a pending change.",
1537 description
=> $opt_force_description,
1539 requires
=> 'delete',
1543 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1549 returns
=> { type
=> 'null' },
1552 &$update_vm_api($param, 1);
1557 __PACKAGE__-
>register_method({
1558 name
=> 'destroy_vm',
1563 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1564 ." and firewall rules",
1566 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1573 skiplock
=> get_standard_option
('skiplock'),
1576 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1579 'destroy-unreferenced-disks' => {
1581 description
=> "If set, destroy additionally all disks not referenced in the config"
1582 ." but with a matching VMID from all enabled storages.",
1584 default => 1, # FIXME: replace to false in PVE 7.0, this is dangerous!
1594 my $rpcenv = PVE
::RPCEnvironment
::get
();
1595 my $authuser = $rpcenv->get_user();
1596 my $vmid = $param->{vmid
};
1598 my $skiplock = $param->{skiplock
};
1599 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1600 if $skiplock && $authuser ne 'root@pam';
1602 my $early_checks = sub {
1604 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1605 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1607 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1609 if (!$param->{purge
}) {
1610 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1612 # don't allow destroy if with replication jobs but no purge param
1613 my $repl_conf = PVE
::ReplicationConfig-
>new();
1614 $repl_conf->check_for_existing_jobs($vmid);
1617 die "VM $vmid is running - destroy failed\n"
1618 if PVE
::QemuServer
::check_running
($vmid);
1628 my $storecfg = PVE
::Storage
::config
();
1630 syslog
('info', "destroy VM $vmid: $upid\n");
1631 PVE
::QemuConfig-
>lock_config($vmid, sub {
1632 # repeat, config might have changed
1633 my $ha_managed = $early_checks->();
1635 # FIXME: drop fallback to true with 7.0, to dangerous for default
1636 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'} // 1;
1638 PVE
::QemuServer
::destroy_vm
(
1641 $skiplock, { lock => 'destroyed' },
1642 $purge_unreferenced,
1645 PVE
::AccessControl
::remove_vm_access
($vmid);
1646 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1647 if ($param->{purge
}) {
1648 print "purging VM $vmid from related configurations..\n";
1649 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1650 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1653 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1654 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1658 # only now remove the zombie config, else we can have reuse race
1659 PVE
::QemuConfig-
>destroy_config($vmid);
1663 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1666 __PACKAGE__-
>register_method({
1668 path
=> '{vmid}/unlink',
1672 description
=> "Unlink/delete disk images.",
1674 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1677 additionalProperties
=> 0,
1679 node
=> get_standard_option
('pve-node'),
1680 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1682 type
=> 'string', format
=> 'pve-configid-list',
1683 description
=> "A list of disk IDs you want to delete.",
1687 description
=> $opt_force_description,
1692 returns
=> { type
=> 'null'},
1696 $param->{delete} = extract_param
($param, 'idlist');
1698 __PACKAGE__-
>update_vm($param);
1703 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1704 my $gen_rand_chars = sub {
1707 die "invalid length $length" if $length < 1;
1709 my $min = ord('!'); # first printable ascii
1711 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1712 die "failed to generate random bytes!\n"
1715 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1722 __PACKAGE__-
>register_method({
1724 path
=> '{vmid}/vncproxy',
1728 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1730 description
=> "Creates a TCP VNC proxy connections.",
1732 additionalProperties
=> 0,
1734 node
=> get_standard_option
('pve-node'),
1735 vmid
=> get_standard_option
('pve-vmid'),
1739 description
=> "starts websockify instead of vncproxy",
1741 'generate-password' => {
1745 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1750 additionalProperties
=> 0,
1752 user
=> { type
=> 'string' },
1753 ticket
=> { type
=> 'string' },
1756 description
=> "Returned if requested with 'generate-password' param."
1757 ." Consists of printable ASCII characters ('!' .. '~').",
1760 cert
=> { type
=> 'string' },
1761 port
=> { type
=> 'integer' },
1762 upid
=> { type
=> 'string' },
1768 my $rpcenv = PVE
::RPCEnvironment
::get
();
1770 my $authuser = $rpcenv->get_user();
1772 my $vmid = $param->{vmid
};
1773 my $node = $param->{node
};
1774 my $websocket = $param->{websocket
};
1776 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1780 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1781 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1784 my $authpath = "/vms/$vmid";
1786 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1787 my $password = $ticket;
1788 if ($param->{'generate-password'}) {
1789 $password = $gen_rand_chars->(8);
1792 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1798 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1799 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1800 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1801 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1802 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1804 $family = PVE
::Tools
::get_host_address_family
($node);
1807 my $port = PVE
::Tools
::next_vnc_port
($family);
1814 syslog
('info', "starting vnc proxy $upid\n");
1818 if (defined($serial)) {
1820 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1822 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1823 '-timeout', $timeout, '-authpath', $authpath,
1824 '-perm', 'Sys.Console'];
1826 if ($param->{websocket
}) {
1827 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1828 push @$cmd, '-notls', '-listen', 'localhost';
1831 push @$cmd, '-c', @$remcmd, @$termcmd;
1833 PVE
::Tools
::run_command
($cmd);
1837 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1839 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1841 my $sock = IO
::Socket
::IP-
>new(
1846 GetAddrInfoFlags
=> 0,
1847 ) or die "failed to create socket: $!\n";
1848 # Inside the worker we shouldn't have any previous alarms
1849 # running anyway...:
1851 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1853 accept(my $cli, $sock) or die "connection failed: $!\n";
1856 if (PVE
::Tools
::run_command
($cmd,
1857 output
=> '>&'.fileno($cli),
1858 input
=> '<&'.fileno($cli),
1861 die "Failed to run vncproxy.\n";
1868 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1870 PVE
::Tools
::wait_for_vnc_port
($port);
1879 $res->{password
} = $password if $param->{'generate-password'};
1884 __PACKAGE__-
>register_method({
1885 name
=> 'termproxy',
1886 path
=> '{vmid}/termproxy',
1890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1892 description
=> "Creates a TCP proxy connections.",
1894 additionalProperties
=> 0,
1896 node
=> get_standard_option
('pve-node'),
1897 vmid
=> get_standard_option
('pve-vmid'),
1901 enum
=> [qw(serial0 serial1 serial2 serial3)],
1902 description
=> "opens a serial terminal (defaults to display)",
1907 additionalProperties
=> 0,
1909 user
=> { type
=> 'string' },
1910 ticket
=> { type
=> 'string' },
1911 port
=> { type
=> 'integer' },
1912 upid
=> { type
=> 'string' },
1918 my $rpcenv = PVE
::RPCEnvironment
::get
();
1920 my $authuser = $rpcenv->get_user();
1922 my $vmid = $param->{vmid
};
1923 my $node = $param->{node
};
1924 my $serial = $param->{serial
};
1926 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1928 if (!defined($serial)) {
1930 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1931 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1935 my $authpath = "/vms/$vmid";
1937 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1942 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1943 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1944 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1945 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1946 push @$remcmd, '--';
1948 $family = PVE
::Tools
::get_host_address_family
($node);
1951 my $port = PVE
::Tools
::next_vnc_port
($family);
1953 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1954 push @$termcmd, '-iface', $serial if $serial;
1959 syslog
('info', "starting qemu termproxy $upid\n");
1961 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1962 '--perm', 'VM.Console', '--'];
1963 push @$cmd, @$remcmd, @$termcmd;
1965 PVE
::Tools
::run_command
($cmd);
1968 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1970 PVE
::Tools
::wait_for_vnc_port
($port);
1980 __PACKAGE__-
>register_method({
1981 name
=> 'vncwebsocket',
1982 path
=> '{vmid}/vncwebsocket',
1985 description
=> "You also need to pass a valid ticket (vncticket).",
1986 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1988 description
=> "Opens a weksocket for VNC traffic.",
1990 additionalProperties
=> 0,
1992 node
=> get_standard_option
('pve-node'),
1993 vmid
=> get_standard_option
('pve-vmid'),
1995 description
=> "Ticket from previous call to vncproxy.",
2000 description
=> "Port number returned by previous vncproxy call.",
2010 port
=> { type
=> 'string' },
2016 my $rpcenv = PVE
::RPCEnvironment
::get
();
2018 my $authuser = $rpcenv->get_user();
2020 my $vmid = $param->{vmid
};
2021 my $node = $param->{node
};
2023 my $authpath = "/vms/$vmid";
2025 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2027 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2029 # Note: VNC ports are acessible from outside, so we do not gain any
2030 # security if we verify that $param->{port} belongs to VM $vmid. This
2031 # check is done by verifying the VNC ticket (inside VNC protocol).
2033 my $port = $param->{port
};
2035 return { port
=> $port };
2038 __PACKAGE__-
>register_method({
2039 name
=> 'spiceproxy',
2040 path
=> '{vmid}/spiceproxy',
2045 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2047 description
=> "Returns a SPICE configuration to connect to the VM.",
2049 additionalProperties
=> 0,
2051 node
=> get_standard_option
('pve-node'),
2052 vmid
=> get_standard_option
('pve-vmid'),
2053 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2056 returns
=> get_standard_option
('remote-viewer-config'),
2060 my $rpcenv = PVE
::RPCEnvironment
::get
();
2062 my $authuser = $rpcenv->get_user();
2064 my $vmid = $param->{vmid
};
2065 my $node = $param->{node
};
2066 my $proxy = $param->{proxy
};
2068 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2069 my $title = "VM $vmid";
2070 $title .= " - ". $conf->{name
} if $conf->{name
};
2072 my $port = PVE
::QemuServer
::spice_port
($vmid);
2074 my ($ticket, undef, $remote_viewer_config) =
2075 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2077 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2078 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2080 return $remote_viewer_config;
2083 __PACKAGE__-
>register_method({
2085 path
=> '{vmid}/status',
2088 description
=> "Directory index",
2093 additionalProperties
=> 0,
2095 node
=> get_standard_option
('pve-node'),
2096 vmid
=> get_standard_option
('pve-vmid'),
2104 subdir
=> { type
=> 'string' },
2107 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2113 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2116 { subdir
=> 'current' },
2117 { subdir
=> 'start' },
2118 { subdir
=> 'stop' },
2119 { subdir
=> 'reset' },
2120 { subdir
=> 'shutdown' },
2121 { subdir
=> 'suspend' },
2122 { subdir
=> 'reboot' },
2128 __PACKAGE__-
>register_method({
2129 name
=> 'vm_status',
2130 path
=> '{vmid}/status/current',
2133 protected
=> 1, # qemu pid files are only readable by root
2134 description
=> "Get virtual machine status.",
2136 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2139 additionalProperties
=> 0,
2141 node
=> get_standard_option
('pve-node'),
2142 vmid
=> get_standard_option
('pve-vmid'),
2148 %$PVE::QemuServer
::vmstatus_return_properties
,
2150 description
=> "HA manager service status.",
2154 description
=> "Qemu VGA configuration supports spice.",
2159 description
=> "Qemu GuestAgent enabled in config.",
2169 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2171 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2172 my $status = $vmstatus->{$param->{vmid
}};
2174 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2176 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2177 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2182 __PACKAGE__-
>register_method({
2184 path
=> '{vmid}/status/start',
2188 description
=> "Start virtual machine.",
2190 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2193 additionalProperties
=> 0,
2195 node
=> get_standard_option
('pve-node'),
2196 vmid
=> get_standard_option
('pve-vmid',
2197 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2198 skiplock
=> get_standard_option
('skiplock'),
2199 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2200 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2203 enum
=> ['secure', 'insecure'],
2204 description
=> "Migration traffic is encrypted using an SSH " .
2205 "tunnel by default. On secure, completely private networks " .
2206 "this can be disabled to increase performance.",
2209 migration_network
=> {
2210 type
=> 'string', format
=> 'CIDR',
2211 description
=> "CIDR of the (sub) network that is used for migration.",
2214 machine
=> get_standard_option
('pve-qemu-machine'),
2216 description
=> "Override QEMU's -cpu argument with the given string.",
2220 targetstorage
=> get_standard_option
('pve-targetstorage'),
2222 description
=> "Wait maximal timeout seconds.",
2225 default => 'max(30, vm memory in GiB)',
2236 my $rpcenv = PVE
::RPCEnvironment
::get
();
2237 my $authuser = $rpcenv->get_user();
2239 my $node = extract_param
($param, 'node');
2240 my $vmid = extract_param
($param, 'vmid');
2241 my $timeout = extract_param
($param, 'timeout');
2243 my $machine = extract_param
($param, 'machine');
2244 my $force_cpu = extract_param
($param, 'force-cpu');
2246 my $get_root_param = sub {
2247 my $value = extract_param
($param, $_[0]);
2248 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2249 if $value && $authuser ne 'root@pam';
2253 my $stateuri = $get_root_param->('stateuri');
2254 my $skiplock = $get_root_param->('skiplock');
2255 my $migratedfrom = $get_root_param->('migratedfrom');
2256 my $migration_type = $get_root_param->('migration_type');
2257 my $migration_network = $get_root_param->('migration_network');
2258 my $targetstorage = $get_root_param->('targetstorage');
2262 if ($targetstorage) {
2263 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2265 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2266 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2270 # read spice ticket from STDIN
2272 my $nbd_protocol_version = 0;
2273 my $replicated_volumes = {};
2274 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2275 while (defined(my $line = <STDIN
>)) {
2277 if ($line =~ m/^spice_ticket: (.+)$/) {
2279 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2280 $nbd_protocol_version = $1;
2281 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2282 $replicated_volumes->{$1} = 1;
2284 # fallback for old source node
2285 $spice_ticket = $line;
2290 PVE
::Cluster
::check_cfs_quorum
();
2292 my $storecfg = PVE
::Storage
::config
();
2294 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2298 print "Requesting HA start for VM $vmid\n";
2300 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2301 PVE
::Tools
::run_command
($cmd);
2305 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2312 syslog
('info', "start VM $vmid: $upid\n");
2314 my $migrate_opts = {
2315 migratedfrom
=> $migratedfrom,
2316 spice_ticket
=> $spice_ticket,
2317 network
=> $migration_network,
2318 type
=> $migration_type,
2319 storagemap
=> $storagemap,
2320 nbd_proto_version
=> $nbd_protocol_version,
2321 replicated_volumes
=> $replicated_volumes,
2325 statefile
=> $stateuri,
2326 skiplock
=> $skiplock,
2327 forcemachine
=> $machine,
2328 timeout
=> $timeout,
2329 forcecpu
=> $force_cpu,
2332 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2336 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2340 __PACKAGE__-
>register_method({
2342 path
=> '{vmid}/status/stop',
2346 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2347 "is akin to pulling the power plug of a running computer and may damage the VM data",
2349 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2352 additionalProperties
=> 0,
2354 node
=> get_standard_option
('pve-node'),
2355 vmid
=> get_standard_option
('pve-vmid',
2356 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2357 skiplock
=> get_standard_option
('skiplock'),
2358 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2360 description
=> "Wait maximal timeout seconds.",
2366 description
=> "Do not deactivate storage volumes.",
2379 my $rpcenv = PVE
::RPCEnvironment
::get
();
2380 my $authuser = $rpcenv->get_user();
2382 my $node = extract_param
($param, 'node');
2383 my $vmid = extract_param
($param, 'vmid');
2385 my $skiplock = extract_param
($param, 'skiplock');
2386 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2387 if $skiplock && $authuser ne 'root@pam';
2389 my $keepActive = extract_param
($param, 'keepActive');
2390 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2391 if $keepActive && $authuser ne 'root@pam';
2393 my $migratedfrom = extract_param
($param, 'migratedfrom');
2394 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2395 if $migratedfrom && $authuser ne 'root@pam';
2398 my $storecfg = PVE
::Storage
::config
();
2400 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2405 print "Requesting HA stop for VM $vmid\n";
2407 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2408 PVE
::Tools
::run_command
($cmd);
2412 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2418 syslog
('info', "stop VM $vmid: $upid\n");
2420 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2421 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2425 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2429 __PACKAGE__-
>register_method({
2431 path
=> '{vmid}/status/reset',
2435 description
=> "Reset virtual machine.",
2437 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2440 additionalProperties
=> 0,
2442 node
=> get_standard_option
('pve-node'),
2443 vmid
=> get_standard_option
('pve-vmid',
2444 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2445 skiplock
=> get_standard_option
('skiplock'),
2454 my $rpcenv = PVE
::RPCEnvironment
::get
();
2456 my $authuser = $rpcenv->get_user();
2458 my $node = extract_param
($param, 'node');
2460 my $vmid = extract_param
($param, 'vmid');
2462 my $skiplock = extract_param
($param, 'skiplock');
2463 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2464 if $skiplock && $authuser ne 'root@pam';
2466 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2471 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2476 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2479 __PACKAGE__-
>register_method({
2480 name
=> 'vm_shutdown',
2481 path
=> '{vmid}/status/shutdown',
2485 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2486 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2488 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2491 additionalProperties
=> 0,
2493 node
=> get_standard_option
('pve-node'),
2494 vmid
=> get_standard_option
('pve-vmid',
2495 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2496 skiplock
=> get_standard_option
('skiplock'),
2498 description
=> "Wait maximal timeout seconds.",
2504 description
=> "Make sure the VM stops.",
2510 description
=> "Do not deactivate storage volumes.",
2523 my $rpcenv = PVE
::RPCEnvironment
::get
();
2524 my $authuser = $rpcenv->get_user();
2526 my $node = extract_param
($param, 'node');
2527 my $vmid = extract_param
($param, 'vmid');
2529 my $skiplock = extract_param
($param, 'skiplock');
2530 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2531 if $skiplock && $authuser ne 'root@pam';
2533 my $keepActive = extract_param
($param, 'keepActive');
2534 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2535 if $keepActive && $authuser ne 'root@pam';
2537 my $storecfg = PVE
::Storage
::config
();
2541 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2542 # otherwise, we will infer a shutdown command, but run into the timeout,
2543 # then when the vm is resumed, it will instantly shutdown
2545 # checking the qmp status here to get feedback to the gui/cli/api
2546 # and the status query should not take too long
2547 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2548 if ($param->{forceStop
}) {
2549 warn "VM is paused - stop instead of shutdown\n";
2552 die "VM is paused - cannot shutdown\n";
2556 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2558 my $timeout = $param->{timeout
} // 60;
2562 print "Requesting HA stop for VM $vmid\n";
2564 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2565 PVE
::Tools
::run_command
($cmd);
2569 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2576 syslog
('info', "shutdown VM $vmid: $upid\n");
2578 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2579 $shutdown, $param->{forceStop
}, $keepActive);
2583 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2587 __PACKAGE__-
>register_method({
2588 name
=> 'vm_reboot',
2589 path
=> '{vmid}/status/reboot',
2593 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2595 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2598 additionalProperties
=> 0,
2600 node
=> get_standard_option
('pve-node'),
2601 vmid
=> get_standard_option
('pve-vmid',
2602 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2604 description
=> "Wait maximal timeout seconds for the shutdown.",
2617 my $rpcenv = PVE
::RPCEnvironment
::get
();
2618 my $authuser = $rpcenv->get_user();
2620 my $node = extract_param
($param, 'node');
2621 my $vmid = extract_param
($param, 'vmid');
2623 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2625 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2630 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2631 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2635 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2638 __PACKAGE__-
>register_method({
2639 name
=> 'vm_suspend',
2640 path
=> '{vmid}/status/suspend',
2644 description
=> "Suspend virtual machine.",
2646 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2647 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2648 " on the storage for the vmstate.",
2649 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2652 additionalProperties
=> 0,
2654 node
=> get_standard_option
('pve-node'),
2655 vmid
=> get_standard_option
('pve-vmid',
2656 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2657 skiplock
=> get_standard_option
('skiplock'),
2662 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2664 statestorage
=> get_standard_option
('pve-storage-id', {
2665 description
=> "The storage for the VM state",
2666 requires
=> 'todisk',
2668 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2678 my $rpcenv = PVE
::RPCEnvironment
::get
();
2679 my $authuser = $rpcenv->get_user();
2681 my $node = extract_param
($param, 'node');
2682 my $vmid = extract_param
($param, 'vmid');
2684 my $todisk = extract_param
($param, 'todisk') // 0;
2686 my $statestorage = extract_param
($param, 'statestorage');
2688 my $skiplock = extract_param
($param, 'skiplock');
2689 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2690 if $skiplock && $authuser ne 'root@pam';
2692 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2694 die "Cannot suspend HA managed VM to disk\n"
2695 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2697 # early check for storage permission, for better user feedback
2699 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2701 if (!$statestorage) {
2702 # get statestorage from config if none is given
2703 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2704 my $storecfg = PVE
::Storage
::config
();
2705 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2708 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2714 syslog
('info', "suspend VM $vmid: $upid\n");
2716 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2721 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2722 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2725 __PACKAGE__-
>register_method({
2726 name
=> 'vm_resume',
2727 path
=> '{vmid}/status/resume',
2731 description
=> "Resume virtual machine.",
2733 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2736 additionalProperties
=> 0,
2738 node
=> get_standard_option
('pve-node'),
2739 vmid
=> get_standard_option
('pve-vmid',
2740 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2741 skiplock
=> get_standard_option
('skiplock'),
2742 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2752 my $rpcenv = PVE
::RPCEnvironment
::get
();
2754 my $authuser = $rpcenv->get_user();
2756 my $node = extract_param
($param, 'node');
2758 my $vmid = extract_param
($param, 'vmid');
2760 my $skiplock = extract_param
($param, 'skiplock');
2761 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2762 if $skiplock && $authuser ne 'root@pam';
2764 my $nocheck = extract_param
($param, 'nocheck');
2765 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2766 if $nocheck && $authuser ne 'root@pam';
2768 my $to_disk_suspended;
2770 PVE
::QemuConfig-
>lock_config($vmid, sub {
2771 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2772 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2776 die "VM $vmid not running\n"
2777 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2782 syslog
('info', "resume VM $vmid: $upid\n");
2784 if (!$to_disk_suspended) {
2785 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2787 my $storecfg = PVE
::Storage
::config
();
2788 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2794 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2797 __PACKAGE__-
>register_method({
2798 name
=> 'vm_sendkey',
2799 path
=> '{vmid}/sendkey',
2803 description
=> "Send key event to virtual machine.",
2805 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2808 additionalProperties
=> 0,
2810 node
=> get_standard_option
('pve-node'),
2811 vmid
=> get_standard_option
('pve-vmid',
2812 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2813 skiplock
=> get_standard_option
('skiplock'),
2815 description
=> "The key (qemu monitor encoding).",
2820 returns
=> { type
=> 'null'},
2824 my $rpcenv = PVE
::RPCEnvironment
::get
();
2826 my $authuser = $rpcenv->get_user();
2828 my $node = extract_param
($param, 'node');
2830 my $vmid = extract_param
($param, 'vmid');
2832 my $skiplock = extract_param
($param, 'skiplock');
2833 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2834 if $skiplock && $authuser ne 'root@pam';
2836 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2841 __PACKAGE__-
>register_method({
2842 name
=> 'vm_feature',
2843 path
=> '{vmid}/feature',
2847 description
=> "Check if feature for virtual machine is available.",
2849 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2852 additionalProperties
=> 0,
2854 node
=> get_standard_option
('pve-node'),
2855 vmid
=> get_standard_option
('pve-vmid'),
2857 description
=> "Feature to check.",
2859 enum
=> [ 'snapshot', 'clone', 'copy' ],
2861 snapname
=> get_standard_option
('pve-snapshot-name', {
2869 hasFeature
=> { type
=> 'boolean' },
2872 items
=> { type
=> 'string' },
2879 my $node = extract_param
($param, 'node');
2881 my $vmid = extract_param
($param, 'vmid');
2883 my $snapname = extract_param
($param, 'snapname');
2885 my $feature = extract_param
($param, 'feature');
2887 my $running = PVE
::QemuServer
::check_running
($vmid);
2889 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2892 my $snap = $conf->{snapshots
}->{$snapname};
2893 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2896 my $storecfg = PVE
::Storage
::config
();
2898 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2899 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2902 hasFeature
=> $hasFeature,
2903 nodes
=> [ keys %$nodelist ],
2907 __PACKAGE__-
>register_method({
2909 path
=> '{vmid}/clone',
2913 description
=> "Create a copy of virtual machine/template.",
2915 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2916 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2917 "'Datastore.AllocateSpace' on any used storage.",
2920 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2922 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2923 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2928 additionalProperties
=> 0,
2930 node
=> get_standard_option
('pve-node'),
2931 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2932 newid
=> get_standard_option
('pve-vmid', {
2933 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2934 description
=> 'VMID for the clone.' }),
2937 type
=> 'string', format
=> 'dns-name',
2938 description
=> "Set a name for the new VM.",
2943 description
=> "Description for the new VM.",
2947 type
=> 'string', format
=> 'pve-poolid',
2948 description
=> "Add the new VM to the specified pool.",
2950 snapname
=> get_standard_option
('pve-snapshot-name', {
2953 storage
=> get_standard_option
('pve-storage-id', {
2954 description
=> "Target storage for full clone.",
2958 description
=> "Target format for file storage. Only valid for full clone.",
2961 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2966 description
=> "Create a full copy of all disks. This is always done when " .
2967 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2969 target
=> get_standard_option
('pve-node', {
2970 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2974 description
=> "Override I/O bandwidth limit (in KiB/s).",
2978 default => 'clone limit from datacenter or storage config',
2988 my $rpcenv = PVE
::RPCEnvironment
::get
();
2989 my $authuser = $rpcenv->get_user();
2991 my $node = extract_param
($param, 'node');
2992 my $vmid = extract_param
($param, 'vmid');
2993 my $newid = extract_param
($param, 'newid');
2994 my $pool = extract_param
($param, 'pool');
2995 $rpcenv->check_pool_exist($pool) if defined($pool);
2997 my $snapname = extract_param
($param, 'snapname');
2998 my $storage = extract_param
($param, 'storage');
2999 my $format = extract_param
($param, 'format');
3000 my $target = extract_param
($param, 'target');
3002 my $localnode = PVE
::INotify
::nodename
();
3004 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3008 PVE
::Cluster
::check_node_exists
($target) if $target;
3010 my $storecfg = PVE
::Storage
::config
();
3013 # check if storage is enabled on local node
3014 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3016 # check if storage is available on target node
3017 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
3018 # clone only works if target storage is shared
3019 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3020 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3024 PVE
::Cluster
::check_cfs_quorum
();
3026 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3029 # do all tests after lock but before forking worker - if possible
3031 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3032 PVE
::QemuConfig-
>check_lock($conf);
3034 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3035 die "unexpected state change\n" if $verify_running != $running;
3037 die "snapshot '$snapname' does not exist\n"
3038 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3040 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3042 die "parameter 'storage' not allowed for linked clones\n"
3043 if defined($storage) && !$full;
3045 die "parameter 'format' not allowed for linked clones\n"
3046 if defined($format) && !$full;
3048 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3050 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3052 die "can't clone VM to node '$target' (VM uses local storage)\n"
3053 if $target && !$sharedvm;
3055 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3056 die "unable to create VM $newid: config file already exists\n"
3059 my $newconf = { lock => 'clone' };
3064 foreach my $opt (keys %$oldconf) {
3065 my $value = $oldconf->{$opt};
3067 # do not copy snapshot related info
3068 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3069 $opt eq 'vmstate' || $opt eq 'snapstate';
3071 # no need to copy unused images, because VMID(owner) changes anyways
3072 next if $opt =~ m/^unused\d+$/;
3074 # always change MAC! address
3075 if ($opt =~ m/^net(\d+)$/) {
3076 my $net = PVE
::QemuServer
::parse_net
($value);
3077 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3078 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3079 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3080 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3081 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3082 die "unable to parse drive options for '$opt'\n" if !$drive;
3083 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3084 $newconf->{$opt} = $value; # simply copy configuration
3086 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3087 die "Full clone feature is not supported for drive '$opt'\n"
3088 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3089 $fullclone->{$opt} = 1;
3091 # not full means clone instead of copy
3092 die "Linked clone feature is not supported for drive '$opt'\n"
3093 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3095 $drives->{$opt} = $drive;
3096 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3097 push @$vollist, $drive->{file
};
3100 # copy everything else
3101 $newconf->{$opt} = $value;
3105 # auto generate a new uuid
3106 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3107 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3108 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3109 # auto generate a new vmgenid only if the option was set for template
3110 if ($newconf->{vmgenid
}) {
3111 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3114 delete $newconf->{template
};
3116 if ($param->{name
}) {
3117 $newconf->{name
} = $param->{name
};
3119 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3122 if ($param->{description
}) {
3123 $newconf->{description
} = $param->{description
};
3126 # create empty/temp config - this fails if VM already exists on other node
3127 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3128 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3133 my $newvollist = [];
3140 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3142 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3144 my $bwlimit = extract_param
($param, 'bwlimit');
3146 my $total_jobs = scalar(keys %{$drives});
3149 foreach my $opt (keys %$drives) {
3150 my $drive = $drives->{$opt};
3151 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3152 my $completion = $skipcomplete ?
'skip' : 'complete';
3154 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3155 my $storage_list = [ $src_sid ];
3156 push @$storage_list, $storage if defined($storage);
3157 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3159 my $newdrive = PVE
::QemuServer
::clone_disk
(
3178 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3180 PVE
::QemuConfig-
>write_config($newid, $newconf);
3184 delete $newconf->{lock};
3186 # do not write pending changes
3187 if (my @changes = keys %{$newconf->{pending
}}) {
3188 my $pending = join(',', @changes);
3189 warn "found pending changes for '$pending', discarding for clone\n";
3190 delete $newconf->{pending
};
3193 PVE
::QemuConfig-
>write_config($newid, $newconf);
3196 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3197 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3198 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3200 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3201 die "Failed to move config to node '$target' - rename failed: $!\n"
3202 if !rename($conffile, $newconffile);
3205 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3208 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3209 sleep 1; # some storage like rbd need to wait before release volume - really?
3211 foreach my $volid (@$newvollist) {
3212 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3216 PVE
::Firewall
::remove_vmfw_conf
($newid);
3218 unlink $conffile; # avoid races -> last thing before die
3220 die "clone failed: $err";
3226 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3228 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3231 # Aquire exclusive lock lock for $newid
3232 my $lock_target_vm = sub {
3233 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3236 # exclusive lock if VM is running - else shared lock is enough;
3238 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3240 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3244 __PACKAGE__-
>register_method({
3245 name
=> 'move_vm_disk',
3246 path
=> '{vmid}/move_disk',
3250 description
=> "Move volume to different storage.",
3252 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3254 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3255 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3259 additionalProperties
=> 0,
3261 node
=> get_standard_option
('pve-node'),
3262 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3265 description
=> "The disk you want to move.",
3266 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3268 storage
=> get_standard_option
('pve-storage-id', {
3269 description
=> "Target storage.",
3270 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3274 description
=> "Target Format.",
3275 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3280 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3286 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3291 description
=> "Override I/O bandwidth limit (in KiB/s).",
3295 default => 'move limit from datacenter or storage config',
3301 description
=> "the task ID.",
3306 my $rpcenv = PVE
::RPCEnvironment
::get
();
3307 my $authuser = $rpcenv->get_user();
3309 my $node = extract_param
($param, 'node');
3310 my $vmid = extract_param
($param, 'vmid');
3311 my $digest = extract_param
($param, 'digest');
3312 my $disk = extract_param
($param, 'disk');
3313 my $storeid = extract_param
($param, 'storage');
3314 my $format = extract_param
($param, 'format');
3316 my $storecfg = PVE
::Storage
::config
();
3318 my $updatefn = sub {
3319 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3320 PVE
::QemuConfig-
>check_lock($conf);
3322 die "VM config checksum missmatch (file change by other user?)\n"
3323 if $digest && $digest ne $conf->{digest
};
3325 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3327 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3329 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3330 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3332 my $old_volid = $drive->{file
};
3334 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3335 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3339 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3340 (!$format || !$oldfmt || $oldfmt eq $format);
3342 # this only checks snapshots because $disk is passed!
3343 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3344 die "you can't move a disk with snapshots and delete the source\n"
3345 if $snapshotted && $param->{delete};
3347 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3349 my $running = PVE
::QemuServer
::check_running
($vmid);
3351 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3354 my $newvollist = [];
3360 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3362 warn "moving disk with snapshots, snapshots will not be moved!\n"
3365 my $bwlimit = extract_param
($param, 'bwlimit');
3366 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3368 my $newdrive = PVE
::QemuServer
::clone_disk
(
3386 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3388 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3390 # convert moved disk to base if part of template
3391 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3392 if PVE
::QemuConfig-
>is_template($conf);
3394 PVE
::QemuConfig-
>write_config($vmid, $conf);
3396 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3397 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3398 eval { mon_cmd
($vmid, "guest-fstrim") };
3402 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3403 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3409 foreach my $volid (@$newvollist) {
3410 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3413 die "storage migration failed: $err";
3416 if ($param->{delete}) {
3418 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3419 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3425 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3428 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3431 my $check_vm_disks_local = sub {
3432 my ($storecfg, $vmconf, $vmid) = @_;
3434 my $local_disks = {};
3436 # add some more information to the disks e.g. cdrom
3437 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3438 my ($volid, $attr) = @_;
3440 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3442 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3443 return if $scfg->{shared
};
3445 # The shared attr here is just a special case where the vdisk
3446 # is marked as shared manually
3447 return if $attr->{shared
};
3448 return if $attr->{cdrom
} and $volid eq "none";
3450 if (exists $local_disks->{$volid}) {
3451 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3453 $local_disks->{$volid} = $attr;
3454 # ensure volid is present in case it's needed
3455 $local_disks->{$volid}->{volid
} = $volid;
3459 return $local_disks;
3462 __PACKAGE__-
>register_method({
3463 name
=> 'migrate_vm_precondition',
3464 path
=> '{vmid}/migrate',
3468 description
=> "Get preconditions for migration.",
3470 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3473 additionalProperties
=> 0,
3475 node
=> get_standard_option
('pve-node'),
3476 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3477 target
=> get_standard_option
('pve-node', {
3478 description
=> "Target node.",
3479 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3487 running
=> { type
=> 'boolean' },
3491 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3493 not_allowed_nodes
=> {
3496 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3500 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3502 local_resources
=> {
3504 description
=> "List local resources e.g. pci, usb"
3511 my $rpcenv = PVE
::RPCEnvironment
::get
();
3513 my $authuser = $rpcenv->get_user();
3515 PVE
::Cluster
::check_cfs_quorum
();
3519 my $vmid = extract_param
($param, 'vmid');
3520 my $target = extract_param
($param, 'target');
3521 my $localnode = PVE
::INotify
::nodename
();
3525 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3526 my $storecfg = PVE
::Storage
::config
();
3529 # try to detect errors early
3530 PVE
::QemuConfig-
>check_lock($vmconf);
3532 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3534 # if vm is not running, return target nodes where local storage is available
3535 # for offline migration
3536 if (!$res->{running
}) {
3537 $res->{allowed_nodes
} = [];
3538 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3539 delete $checked_nodes->{$localnode};
3541 foreach my $node (keys %$checked_nodes) {
3542 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3543 push @{$res->{allowed_nodes
}}, $node;
3547 $res->{not_allowed_nodes
} = $checked_nodes;
3551 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3552 $res->{local_disks
} = [ values %$local_disks ];;
3554 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3556 $res->{local_resources
} = $local_resources;
3563 __PACKAGE__-
>register_method({
3564 name
=> 'migrate_vm',
3565 path
=> '{vmid}/migrate',
3569 description
=> "Migrate virtual machine. Creates a new migration task.",
3571 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3574 additionalProperties
=> 0,
3576 node
=> get_standard_option
('pve-node'),
3577 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3578 target
=> get_standard_option
('pve-node', {
3579 description
=> "Target node.",
3580 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3584 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3589 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3594 enum
=> ['secure', 'insecure'],
3595 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3598 migration_network
=> {
3599 type
=> 'string', format
=> 'CIDR',
3600 description
=> "CIDR of the (sub) network that is used for migration.",
3603 "with-local-disks" => {
3605 description
=> "Enable live storage migration for local disk",
3608 targetstorage
=> get_standard_option
('pve-targetstorage', {
3609 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3612 description
=> "Override I/O bandwidth limit (in KiB/s).",
3616 default => 'migrate limit from datacenter or storage config',
3622 description
=> "the task ID.",
3627 my $rpcenv = PVE
::RPCEnvironment
::get
();
3628 my $authuser = $rpcenv->get_user();
3630 my $target = extract_param
($param, 'target');
3632 my $localnode = PVE
::INotify
::nodename
();
3633 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3635 PVE
::Cluster
::check_cfs_quorum
();
3637 PVE
::Cluster
::check_node_exists
($target);
3639 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3641 my $vmid = extract_param
($param, 'vmid');
3643 raise_param_exc
({ force
=> "Only root may use this option." })
3644 if $param->{force
} && $authuser ne 'root@pam';
3646 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3647 if $param->{migration_type
} && $authuser ne 'root@pam';
3649 # allow root only until better network permissions are available
3650 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3651 if $param->{migration_network
} && $authuser ne 'root@pam';
3654 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3656 # try to detect errors early
3658 PVE
::QemuConfig-
>check_lock($conf);
3660 if (PVE
::QemuServer
::check_running
($vmid)) {
3661 die "can't migrate running VM without --online\n" if !$param->{online
};
3663 my $repl_conf = PVE
::ReplicationConfig-
>new();
3664 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3665 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3666 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3667 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3668 "target. Use 'force' to override.\n";
3671 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3672 $param->{online
} = 0;
3675 my $storecfg = PVE
::Storage
::config
();
3677 if (my $targetstorage = $param->{targetstorage
}) {
3678 my $check_storage = sub {
3679 my ($target_sid) = @_;
3680 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3681 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3682 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3683 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3684 if !$scfg->{content
}->{images
};
3687 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3688 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3691 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3692 if !defined($storagemap->{identity
});
3694 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3695 $check_storage->($target_sid);
3698 $check_storage->($storagemap->{default})
3699 if $storagemap->{default};
3701 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3702 if $storagemap->{identity
};
3704 $param->{storagemap
} = $storagemap;
3706 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3709 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3714 print "Requesting HA migration for VM $vmid to node $target\n";
3716 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3717 PVE
::Tools
::run_command
($cmd);
3721 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3726 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3730 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3733 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3738 __PACKAGE__-
>register_method({
3740 path
=> '{vmid}/monitor',
3744 description
=> "Execute Qemu monitor commands.",
3746 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3747 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3750 additionalProperties
=> 0,
3752 node
=> get_standard_option
('pve-node'),
3753 vmid
=> get_standard_option
('pve-vmid'),
3756 description
=> "The monitor command.",
3760 returns
=> { type
=> 'string'},
3764 my $rpcenv = PVE
::RPCEnvironment
::get
();
3765 my $authuser = $rpcenv->get_user();
3768 my $command = shift;
3769 return $command =~ m/^\s*info(\s+|$)/
3770 || $command =~ m/^\s*help\s*$/;
3773 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3774 if !&$is_ro($param->{command
});
3776 my $vmid = $param->{vmid
};
3778 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3782 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3784 $res = "ERROR: $@" if $@;
3789 __PACKAGE__-
>register_method({
3790 name
=> 'resize_vm',
3791 path
=> '{vmid}/resize',
3795 description
=> "Extend volume size.",
3797 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3800 additionalProperties
=> 0,
3802 node
=> get_standard_option
('pve-node'),
3803 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3804 skiplock
=> get_standard_option
('skiplock'),
3807 description
=> "The disk you want to resize.",
3808 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3812 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3813 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.",
3817 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3823 returns
=> { type
=> 'null'},
3827 my $rpcenv = PVE
::RPCEnvironment
::get
();
3829 my $authuser = $rpcenv->get_user();
3831 my $node = extract_param
($param, 'node');
3833 my $vmid = extract_param
($param, 'vmid');
3835 my $digest = extract_param
($param, 'digest');
3837 my $disk = extract_param
($param, 'disk');
3839 my $sizestr = extract_param
($param, 'size');
3841 my $skiplock = extract_param
($param, 'skiplock');
3842 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3843 if $skiplock && $authuser ne 'root@pam';
3845 my $storecfg = PVE
::Storage
::config
();
3847 my $updatefn = sub {
3849 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3851 die "checksum missmatch (file change by other user?)\n"
3852 if $digest && $digest ne $conf->{digest
};
3853 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3855 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3857 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3859 my (undef, undef, undef, undef, undef, undef, $format) =
3860 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3862 die "can't resize volume: $disk if snapshot exists\n"
3863 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3865 my $volid = $drive->{file
};
3867 die "disk '$disk' has no associated volume\n" if !$volid;
3869 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3871 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3873 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3875 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3876 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3878 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3880 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3881 my ($ext, $newsize, $unit) = ($1, $2, $4);
3884 $newsize = $newsize * 1024;
3885 } elsif ($unit eq 'M') {
3886 $newsize = $newsize * 1024 * 1024;
3887 } elsif ($unit eq 'G') {
3888 $newsize = $newsize * 1024 * 1024 * 1024;
3889 } elsif ($unit eq 'T') {
3890 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3893 $newsize += $size if $ext;
3894 $newsize = int($newsize);
3896 die "shrinking disks is not supported\n" if $newsize < $size;
3898 return if $size == $newsize;
3900 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3902 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3904 $drive->{size
} = $newsize;
3905 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3907 PVE
::QemuConfig-
>write_config($vmid, $conf);
3910 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3914 __PACKAGE__-
>register_method({
3915 name
=> 'snapshot_list',
3916 path
=> '{vmid}/snapshot',
3918 description
=> "List all snapshots.",
3920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3923 protected
=> 1, # qemu pid files are only readable by root
3925 additionalProperties
=> 0,
3927 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3928 node
=> get_standard_option
('pve-node'),
3937 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3941 description
=> "Snapshot includes RAM.",
3946 description
=> "Snapshot description.",
3950 description
=> "Snapshot creation time",
3952 renderer
=> 'timestamp',
3956 description
=> "Parent snapshot identifier.",
3962 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3967 my $vmid = $param->{vmid
};
3969 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3970 my $snaphash = $conf->{snapshots
} || {};
3974 foreach my $name (keys %$snaphash) {
3975 my $d = $snaphash->{$name};
3978 snaptime
=> $d->{snaptime
} || 0,
3979 vmstate
=> $d->{vmstate
} ?
1 : 0,
3980 description
=> $d->{description
} || '',
3982 $item->{parent
} = $d->{parent
} if $d->{parent
};
3983 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3987 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3990 digest
=> $conf->{digest
},
3991 running
=> $running,
3992 description
=> "You are here!",
3994 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3996 push @$res, $current;
4001 __PACKAGE__-
>register_method({
4003 path
=> '{vmid}/snapshot',
4007 description
=> "Snapshot a VM.",
4009 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4012 additionalProperties
=> 0,
4014 node
=> get_standard_option
('pve-node'),
4015 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4016 snapname
=> get_standard_option
('pve-snapshot-name'),
4020 description
=> "Save the vmstate",
4025 description
=> "A textual description or comment.",
4031 description
=> "the task ID.",
4036 my $rpcenv = PVE
::RPCEnvironment
::get
();
4038 my $authuser = $rpcenv->get_user();
4040 my $node = extract_param
($param, 'node');
4042 my $vmid = extract_param
($param, 'vmid');
4044 my $snapname = extract_param
($param, 'snapname');
4046 die "unable to use snapshot name 'current' (reserved name)\n"
4047 if $snapname eq 'current';
4049 die "unable to use snapshot name 'pending' (reserved name)\n"
4050 if lc($snapname) eq 'pending';
4053 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4054 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4055 $param->{description
});
4058 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4061 __PACKAGE__-
>register_method({
4062 name
=> 'snapshot_cmd_idx',
4063 path
=> '{vmid}/snapshot/{snapname}',
4070 additionalProperties
=> 0,
4072 vmid
=> get_standard_option
('pve-vmid'),
4073 node
=> get_standard_option
('pve-node'),
4074 snapname
=> get_standard_option
('pve-snapshot-name'),
4083 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4090 push @$res, { cmd
=> 'rollback' };
4091 push @$res, { cmd
=> 'config' };
4096 __PACKAGE__-
>register_method({
4097 name
=> 'update_snapshot_config',
4098 path
=> '{vmid}/snapshot/{snapname}/config',
4102 description
=> "Update snapshot metadata.",
4104 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4107 additionalProperties
=> 0,
4109 node
=> get_standard_option
('pve-node'),
4110 vmid
=> get_standard_option
('pve-vmid'),
4111 snapname
=> get_standard_option
('pve-snapshot-name'),
4115 description
=> "A textual description or comment.",
4119 returns
=> { type
=> 'null' },
4123 my $rpcenv = PVE
::RPCEnvironment
::get
();
4125 my $authuser = $rpcenv->get_user();
4127 my $vmid = extract_param
($param, 'vmid');
4129 my $snapname = extract_param
($param, 'snapname');
4131 return if !defined($param->{description
});
4133 my $updatefn = sub {
4135 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4137 PVE
::QemuConfig-
>check_lock($conf);
4139 my $snap = $conf->{snapshots
}->{$snapname};
4141 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4143 $snap->{description
} = $param->{description
} if defined($param->{description
});
4145 PVE
::QemuConfig-
>write_config($vmid, $conf);
4148 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4153 __PACKAGE__-
>register_method({
4154 name
=> 'get_snapshot_config',
4155 path
=> '{vmid}/snapshot/{snapname}/config',
4158 description
=> "Get snapshot configuration",
4160 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4163 additionalProperties
=> 0,
4165 node
=> get_standard_option
('pve-node'),
4166 vmid
=> get_standard_option
('pve-vmid'),
4167 snapname
=> get_standard_option
('pve-snapshot-name'),
4170 returns
=> { type
=> "object" },
4174 my $rpcenv = PVE
::RPCEnvironment
::get
();
4176 my $authuser = $rpcenv->get_user();
4178 my $vmid = extract_param
($param, 'vmid');
4180 my $snapname = extract_param
($param, 'snapname');
4182 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4184 my $snap = $conf->{snapshots
}->{$snapname};
4186 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4191 __PACKAGE__-
>register_method({
4193 path
=> '{vmid}/snapshot/{snapname}/rollback',
4197 description
=> "Rollback VM state to specified snapshot.",
4199 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4202 additionalProperties
=> 0,
4204 node
=> get_standard_option
('pve-node'),
4205 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4206 snapname
=> get_standard_option
('pve-snapshot-name'),
4211 description
=> "the task ID.",
4216 my $rpcenv = PVE
::RPCEnvironment
::get
();
4218 my $authuser = $rpcenv->get_user();
4220 my $node = extract_param
($param, 'node');
4222 my $vmid = extract_param
($param, 'vmid');
4224 my $snapname = extract_param
($param, 'snapname');
4227 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4228 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4232 # hold migration lock, this makes sure that nobody create replication snapshots
4233 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4236 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4239 __PACKAGE__-
>register_method({
4240 name
=> 'delsnapshot',
4241 path
=> '{vmid}/snapshot/{snapname}',
4245 description
=> "Delete a VM snapshot.",
4247 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4250 additionalProperties
=> 0,
4252 node
=> get_standard_option
('pve-node'),
4253 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4254 snapname
=> get_standard_option
('pve-snapshot-name'),
4258 description
=> "For removal from config file, even if removing disk snapshots fails.",
4264 description
=> "the task ID.",
4269 my $rpcenv = PVE
::RPCEnvironment
::get
();
4271 my $authuser = $rpcenv->get_user();
4273 my $node = extract_param
($param, 'node');
4275 my $vmid = extract_param
($param, 'vmid');
4277 my $snapname = extract_param
($param, 'snapname');
4280 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4281 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4284 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4287 __PACKAGE__-
>register_method({
4289 path
=> '{vmid}/template',
4293 description
=> "Create a Template.",
4295 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4296 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4299 additionalProperties
=> 0,
4301 node
=> get_standard_option
('pve-node'),
4302 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4306 description
=> "If you want to convert only 1 disk to base image.",
4307 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4312 returns
=> { type
=> 'null'},
4316 my $rpcenv = PVE
::RPCEnvironment
::get
();
4318 my $authuser = $rpcenv->get_user();
4320 my $node = extract_param
($param, 'node');
4322 my $vmid = extract_param
($param, 'vmid');
4324 my $disk = extract_param
($param, 'disk');
4326 my $updatefn = sub {
4328 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4330 PVE
::QemuConfig-
>check_lock($conf);
4332 die "unable to create template, because VM contains snapshots\n"
4333 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4335 die "you can't convert a template to a template\n"
4336 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4338 die "you can't convert a VM to template if VM is running\n"
4339 if PVE
::QemuServer
::check_running
($vmid);
4342 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4345 $conf->{template
} = 1;
4346 PVE
::QemuConfig-
>write_config($vmid, $conf);
4348 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4351 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4355 __PACKAGE__-
>register_method({
4356 name
=> 'cloudinit_generated_config_dump',
4357 path
=> '{vmid}/cloudinit/dump',
4360 description
=> "Get automatically generated cloudinit config.",
4362 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4365 additionalProperties
=> 0,
4367 node
=> get_standard_option
('pve-node'),
4368 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4370 description
=> 'Config type.',
4372 enum
=> ['user', 'network', 'meta'],
4382 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4384 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});