1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
27 use PVE
::QemuServer
::Machine
;
29 use PVE
::RPCEnvironment
;
30 use PVE
::AccessControl
;
34 use PVE
::API2
::Firewall
::VM
;
35 use PVE
::API2
::Qemu
::Agent
;
36 use PVE
::VZDump
::Plugin
;
37 use PVE
::DataCenterConfig
;
42 if (!$ENV{PVE_GENERATING_DOCS
}) {
43 require PVE
::HA
::Env
::PVE2
;
44 import PVE
::HA
::Env
::PVE2
;
45 require PVE
::HA
::Config
;
46 import PVE
::HA
::Config
;
50 use Data
::Dumper
; # fixme: remove
52 use base
qw(PVE::RESTHandler);
54 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.";
56 my $resolve_cdrom_alias = sub {
59 if (my $value = $param->{cdrom
}) {
60 $value .= ",media=cdrom" if $value !~ m/media=/;
61 $param->{ide2
} = $value;
62 delete $param->{cdrom
};
66 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
67 my $check_storage_access = sub {
68 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
70 PVE
::QemuConfig-
>foreach_volume($settings, sub {
71 my ($ds, $drive) = @_;
73 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
75 my $volid = $drive->{file
};
76 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
78 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
80 } elsif ($isCDROM && ($volid eq 'cdrom')) {
81 $rpcenv->check($authuser, "/", ['Sys.Console']);
82 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
83 my ($storeid, $size) = ($2 || $default_storage, $3);
84 die "no storage ID specified (and no default storage)\n" if !$storeid;
85 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
86 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
87 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
88 if !$scfg->{content
}->{images
};
90 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
94 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
95 if defined($settings->{vmstatestorage
});
98 my $check_storage_access_clone = sub {
99 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
103 PVE
::QemuConfig-
>foreach_volume($conf, sub {
104 my ($ds, $drive) = @_;
106 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
108 my $volid = $drive->{file
};
110 return if !$volid || $volid eq 'none';
113 if ($volid eq 'cdrom') {
114 $rpcenv->check($authuser, "/", ['Sys.Console']);
116 # we simply allow access
117 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
118 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
119 $sharedvm = 0 if !$scfg->{shared
};
123 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
124 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
125 $sharedvm = 0 if !$scfg->{shared
};
127 $sid = $storage if $storage;
128 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
132 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
133 if defined($conf->{vmstatestorage
});
138 my $check_storage_access_migrate = sub {
139 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
141 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
143 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
145 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
146 die "storage '$storage' does not support vm images\n"
147 if !$scfg->{content
}->{images
};
150 # Note: $pool is only needed when creating a VM, because pool permissions
151 # are automatically inherited if VM already exists inside a pool.
152 my $create_disks = sub {
153 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
160 my ($ds, $disk) = @_;
162 my $volid = $disk->{file
};
163 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
165 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
166 delete $disk->{size
};
167 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
168 } elsif (defined($volname) && $volname eq 'cloudinit') {
169 $storeid = $storeid // $default_storage;
170 die "no storage ID specified (and no default storage)\n" if !$storeid;
171 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
172 my $name = "vm-$vmid-cloudinit";
176 $fmt = $disk->{format
} // "qcow2";
179 $fmt = $disk->{format
} // "raw";
182 # Initial disk created with 4 MB and aligned to 4MB on regeneration
183 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
184 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
185 $disk->{file
} = $volid;
186 $disk->{media
} = 'cdrom';
187 push @$vollist, $volid;
188 delete $disk->{format
}; # no longer needed
189 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
190 } elsif ($volid =~ $NEW_DISK_RE) {
191 my ($storeid, $size) = ($2 || $default_storage, $3);
192 die "no storage ID specified (and no default storage)\n" if !$storeid;
193 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
194 my $fmt = $disk->{format
} || $defformat;
196 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
199 if ($ds eq 'efidisk0') {
200 my $smm = PVE
::QemuServer
::Machine
::machine_type_is_q35
($conf);
201 ($volid, $size) = PVE
::QemuServer
::create_efidisk
(
202 $storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm);
203 } elsif ($ds eq 'tpmstate0') {
204 # swtpm can only use raw volumes, and uses a fixed size
205 $size = PVE
::Tools
::convert_size
(PVE
::QemuServer
::Drive
::TPMSTATE_DISK_SIZE
, 'b' => 'kb');
206 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, "raw", undef, $size);
208 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
210 push @$vollist, $volid;
211 $disk->{file
} = $volid;
212 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
213 delete $disk->{format
}; # no longer needed
214 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
217 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
219 my $volid_is_new = 1;
222 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
223 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
228 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
230 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
232 die "volume $volid does not exist\n" if !$size;
234 $disk->{size
} = $size;
237 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
241 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
243 # free allocated images on error
245 syslog
('err', "VM $vmid creating disks failed");
246 foreach my $volid (@$vollist) {
247 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
253 # modify vm config if everything went well
254 foreach my $ds (keys %$res) {
255 $conf->{$ds} = $res->{$ds};
261 my $check_cpu_model_access = sub {
262 my ($rpcenv, $authuser, $new, $existing) = @_;
264 return if !defined($new->{cpu
});
266 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
267 return if !$cpu || !$cpu->{cputype
}; # always allow default
268 my $cputype = $cpu->{cputype
};
270 if ($existing && $existing->{cpu
}) {
271 # changing only other settings doesn't require permissions for CPU model
272 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
273 return if $existingCpu->{cputype
} eq $cputype;
276 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
277 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
292 my $memoryoptions = {
298 my $hwtypeoptions = {
311 my $generaloptions = {
318 'migrate_downtime' => 1,
319 'migrate_speed' => 1,
332 my $vmpoweroptions = {
339 'vmstatestorage' => 1,
342 my $cloudinitoptions = {
352 my $check_vm_create_serial_perm = sub {
353 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
355 return 1 if $authuser eq 'root@pam';
357 foreach my $opt (keys %{$param}) {
358 next if $opt !~ m/^serial\d+$/;
360 if ($param->{$opt} eq 'socket') {
361 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
363 die "only root can set '$opt' config for real devices\n";
370 my $check_vm_create_usb_perm = sub {
371 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
373 return 1 if $authuser eq 'root@pam';
375 foreach my $opt (keys %{$param}) {
376 next if $opt !~ m/^usb\d+$/;
378 if ($param->{$opt} =~ m/spice/) {
379 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
381 die "only root can set '$opt' config for real devices\n";
388 my $check_vm_modify_config_perm = sub {
389 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
391 return 1 if $authuser eq 'root@pam';
393 foreach my $opt (@$key_list) {
394 # some checks (e.g., disk, serial port, usb) need to be done somewhere
395 # else, as there the permission can be value dependend
396 next if PVE
::QemuServer
::is_valid_drivename
($opt);
397 next if $opt eq 'cdrom';
398 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
401 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
402 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
403 } elsif ($memoryoptions->{$opt}) {
404 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
405 } elsif ($hwtypeoptions->{$opt}) {
406 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
407 } elsif ($generaloptions->{$opt}) {
408 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
409 # special case for startup since it changes host behaviour
410 if ($opt eq 'startup') {
411 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
413 } elsif ($vmpoweroptions->{$opt}) {
414 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
415 } elsif ($diskoptions->{$opt}) {
416 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
417 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
418 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
419 } elsif ($cloudinitoptions->{$opt}) {
420 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
421 } elsif ($opt eq 'vmstate') {
422 # the user needs Disk and PowerMgmt privileges to change the vmstate
423 # also needs privileges on the storage, that will be checked later
424 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
426 # catches hostpci\d+, args, lock, etc.
427 # new options will be checked here
428 die "only root can set '$opt' config\n";
435 __PACKAGE__-
>register_method({
439 description
=> "Virtual machine index (per node).",
441 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
445 protected
=> 1, # qemu pid files are only readable by root
447 additionalProperties
=> 0,
449 node
=> get_standard_option
('pve-node'),
453 description
=> "Determine the full status of active VMs.",
461 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
463 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
468 my $rpcenv = PVE
::RPCEnvironment
::get
();
469 my $authuser = $rpcenv->get_user();
471 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
474 foreach my $vmid (keys %$vmstatus) {
475 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
477 my $data = $vmstatus->{$vmid};
484 my $parse_restore_archive = sub {
485 my ($storecfg, $archive) = @_;
487 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
489 if (defined($archive_storeid)) {
490 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
491 if ($scfg->{type
} eq 'pbs') {
498 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
506 __PACKAGE__-
>register_method({
510 description
=> "Create or restore a virtual machine.",
512 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
513 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
514 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
515 user
=> 'all', # check inside
520 additionalProperties
=> 0,
521 properties
=> PVE
::QemuServer
::json_config_properties
(
523 node
=> get_standard_option
('pve-node'),
524 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
526 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.",
530 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
532 storage
=> get_standard_option
('pve-storage-id', {
533 description
=> "Default storage.",
535 completion
=> \
&PVE
::QemuServer
::complete_storage
,
540 description
=> "Allow to overwrite existing VM.",
541 requires
=> 'archive',
546 description
=> "Assign a unique random ethernet address.",
547 requires
=> 'archive',
552 description
=> "Start the VM immediately from the backup and restore in background. PBS only.",
553 requires
=> 'archive',
557 type
=> 'string', format
=> 'pve-poolid',
558 description
=> "Add the VM to the specified pool.",
561 description
=> "Override I/O bandwidth limit (in KiB/s).",
565 default => 'restore limit from datacenter or storage config',
571 description
=> "Start VM after it was created successfully.",
581 my $rpcenv = PVE
::RPCEnvironment
::get
();
582 my $authuser = $rpcenv->get_user();
584 my $node = extract_param
($param, 'node');
585 my $vmid = extract_param
($param, 'vmid');
587 my $archive = extract_param
($param, 'archive');
588 my $is_restore = !!$archive;
590 my $bwlimit = extract_param
($param, 'bwlimit');
591 my $force = extract_param
($param, 'force');
592 my $pool = extract_param
($param, 'pool');
593 my $start_after_create = extract_param
($param, 'start');
594 my $storage = extract_param
($param, 'storage');
595 my $unique = extract_param
($param, 'unique');
596 my $live_restore = extract_param
($param, 'live-restore');
598 if (defined(my $ssh_keys = $param->{sshkeys
})) {
599 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
600 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
603 PVE
::Cluster
::check_cfs_quorum
();
605 my $filename = PVE
::QemuConfig-
>config_file($vmid);
606 my $storecfg = PVE
::Storage
::config
();
608 if (defined($pool)) {
609 $rpcenv->check_pool_exist($pool);
612 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
613 if defined($storage);
615 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
617 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
619 } elsif ($archive && $force && (-f
$filename) &&
620 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
621 # OK: user has VM.Backup permissions, and want to restore an existing VM
627 &$resolve_cdrom_alias($param);
629 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
631 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
633 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
634 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
636 &$check_cpu_model_access($rpcenv, $authuser, $param);
638 foreach my $opt (keys %$param) {
639 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
640 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
641 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
643 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
644 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
648 PVE
::QemuServer
::add_random_macs
($param);
650 my $keystr = join(' ', keys %$param);
651 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
653 if ($archive eq '-') {
654 die "pipe requires cli environment\n" if $rpcenv->{type
} ne 'cli';
655 $archive = { type
=> 'pipe' };
657 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
659 $archive = $parse_restore_archive->($storecfg, $archive);
663 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
665 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
666 die "$emsg $@" if $@;
668 my $restored_data = 0;
669 my $restorefn = sub {
670 my $conf = PVE
::QemuConfig-
>load_config($vmid);
672 PVE
::QemuConfig-
>check_protection($conf, $emsg);
674 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
677 my $restore_options = {
682 live
=> $live_restore,
684 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
685 die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
687 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
688 } elsif ($archive->{type
} eq 'pbs') {
689 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
691 die "unknown backup archive type\n";
695 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
696 # Convert restored VM to template if backup was VM template
697 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
698 warn "Convert to template.\n";
699 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
704 # ensure no old replication state are exists
705 PVE
::ReplicationState
::delete_guest_states
($vmid);
707 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
709 if ($start_after_create && !$live_restore) {
710 print "Execute autostart\n";
711 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
717 # ensure no old replication state are exists
718 PVE
::ReplicationState
::delete_guest_states
($vmid);
722 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
724 $conf->{meta
} = PVE
::QemuServer
::new_meta_info_string
();
728 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
730 if (!$conf->{boot
}) {
731 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
732 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
735 # auto generate uuid if user did not specify smbios1 option
736 if (!$conf->{smbios1
}) {
737 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
740 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
741 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
744 my $machine = $conf->{machine
};
745 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
746 # always pin Windows' machine version on create, they get to easily confused
747 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
748 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
752 PVE
::QemuConfig-
>write_config($vmid, $conf);
758 foreach my $volid (@$vollist) {
759 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
765 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
768 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
770 if ($start_after_create) {
771 print "Execute autostart\n";
772 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
777 my ($code, $worker_name);
779 $worker_name = 'qmrestore';
781 eval { $restorefn->() };
783 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
785 if ($restored_data) {
786 warn "error after data was restored, VM disks should be OK but config may "
787 ."require adaptions. VM $vmid state is NOT cleaned up.\n";
789 warn "error before or during data restore, some or all disks were not "
790 ."completely restored. VM $vmid state is NOT cleaned up.\n";
796 $worker_name = 'qmcreate';
798 eval { $createfn->() };
801 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
802 unlink($conffile) or die "failed to remove config file: $!\n";
810 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
813 __PACKAGE__-
>register_method({
818 description
=> "Directory index",
823 additionalProperties
=> 0,
825 node
=> get_standard_option
('pve-node'),
826 vmid
=> get_standard_option
('pve-vmid'),
834 subdir
=> { type
=> 'string' },
837 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
843 { subdir
=> 'config' },
844 { subdir
=> 'pending' },
845 { subdir
=> 'status' },
846 { subdir
=> 'unlink' },
847 { subdir
=> 'vncproxy' },
848 { subdir
=> 'termproxy' },
849 { subdir
=> 'migrate' },
850 { subdir
=> 'resize' },
851 { subdir
=> 'move' },
853 { subdir
=> 'rrddata' },
854 { subdir
=> 'monitor' },
855 { subdir
=> 'agent' },
856 { subdir
=> 'snapshot' },
857 { subdir
=> 'spiceproxy' },
858 { subdir
=> 'sendkey' },
859 { subdir
=> 'firewall' },
865 __PACKAGE__-
>register_method ({
866 subclass
=> "PVE::API2::Firewall::VM",
867 path
=> '{vmid}/firewall',
870 __PACKAGE__-
>register_method ({
871 subclass
=> "PVE::API2::Qemu::Agent",
872 path
=> '{vmid}/agent',
875 __PACKAGE__-
>register_method({
877 path
=> '{vmid}/rrd',
879 protected
=> 1, # fixme: can we avoid that?
881 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
883 description
=> "Read VM RRD statistics (returns PNG)",
885 additionalProperties
=> 0,
887 node
=> get_standard_option
('pve-node'),
888 vmid
=> get_standard_option
('pve-vmid'),
890 description
=> "Specify the time frame you are interested in.",
892 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
895 description
=> "The list of datasources you want to display.",
896 type
=> 'string', format
=> 'pve-configid-list',
899 description
=> "The RRD consolidation function",
901 enum
=> [ 'AVERAGE', 'MAX' ],
909 filename
=> { type
=> 'string' },
915 return PVE
::RRD
::create_rrd_graph
(
916 "pve2-vm/$param->{vmid}", $param->{timeframe
},
917 $param->{ds
}, $param->{cf
});
921 __PACKAGE__-
>register_method({
923 path
=> '{vmid}/rrddata',
925 protected
=> 1, # fixme: can we avoid that?
927 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
929 description
=> "Read VM RRD statistics",
931 additionalProperties
=> 0,
933 node
=> get_standard_option
('pve-node'),
934 vmid
=> get_standard_option
('pve-vmid'),
936 description
=> "Specify the time frame you are interested in.",
938 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
941 description
=> "The RRD consolidation function",
943 enum
=> [ 'AVERAGE', 'MAX' ],
958 return PVE
::RRD
::create_rrd_data
(
959 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
963 __PACKAGE__-
>register_method({
965 path
=> '{vmid}/config',
968 description
=> "Get the virtual machine configuration with pending configuration " .
969 "changes applied. Set the 'current' parameter to get the current configuration instead.",
971 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
974 additionalProperties
=> 0,
976 node
=> get_standard_option
('pve-node'),
977 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
979 description
=> "Get current values (instead of pending values).",
984 snapshot
=> get_standard_option
('pve-snapshot-name', {
985 description
=> "Fetch config values from given snapshot.",
988 my ($cmd, $pname, $cur, $args) = @_;
989 PVE
::QemuConfig-
>snapshot_list($args->[0]);
995 description
=> "The VM configuration.",
997 properties
=> PVE
::QemuServer
::json_config_properties
({
1000 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
1007 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
1008 current
=> "cannot use 'snapshot' parameter with 'current'"})
1009 if ($param->{snapshot
} && $param->{current
});
1012 if ($param->{snapshot
}) {
1013 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
1015 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
1017 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
1022 __PACKAGE__-
>register_method({
1023 name
=> 'vm_pending',
1024 path
=> '{vmid}/pending',
1027 description
=> "Get the virtual machine configuration with both current and pending values.",
1029 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1032 additionalProperties
=> 0,
1034 node
=> get_standard_option
('pve-node'),
1035 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1044 description
=> "Configuration option name.",
1048 description
=> "Current value.",
1053 description
=> "Pending value.",
1058 description
=> "Indicates a pending delete request if present and not 0. " .
1059 "The value 2 indicates a force-delete request.",
1071 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1073 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1075 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1076 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1078 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1081 # POST/PUT {vmid}/config implementation
1083 # The original API used PUT (idempotent) an we assumed that all operations
1084 # are fast. But it turned out that almost any configuration change can
1085 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1086 # time to complete and have side effects (not idempotent).
1088 # The new implementation uses POST and forks a worker process. We added
1089 # a new option 'background_delay'. If specified we wait up to
1090 # 'background_delay' second for the worker task to complete. It returns null
1091 # if the task is finished within that time, else we return the UPID.
1093 my $update_vm_api = sub {
1094 my ($param, $sync) = @_;
1096 my $rpcenv = PVE
::RPCEnvironment
::get
();
1098 my $authuser = $rpcenv->get_user();
1100 my $node = extract_param
($param, 'node');
1102 my $vmid = extract_param
($param, 'vmid');
1104 my $digest = extract_param
($param, 'digest');
1106 my $background_delay = extract_param
($param, 'background_delay');
1108 if (defined(my $cipassword = $param->{cipassword
})) {
1109 # Same logic as in cloud-init (but with the regex fixed...)
1110 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1111 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1114 my @paramarr = (); # used for log message
1115 foreach my $key (sort keys %$param) {
1116 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1117 push @paramarr, "-$key", $value;
1120 my $skiplock = extract_param
($param, 'skiplock');
1121 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1122 if $skiplock && $authuser ne 'root@pam';
1124 my $delete_str = extract_param
($param, 'delete');
1126 my $revert_str = extract_param
($param, 'revert');
1128 my $force = extract_param
($param, 'force');
1130 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1131 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1132 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1135 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1137 my $storecfg = PVE
::Storage
::config
();
1139 my $defaults = PVE
::QemuServer
::load_defaults
();
1141 &$resolve_cdrom_alias($param);
1143 # now try to verify all parameters
1146 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1147 if (!PVE
::QemuServer
::option_exists
($opt)) {
1148 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1151 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1152 "-revert $opt' at the same time" })
1153 if defined($param->{$opt});
1155 $revert->{$opt} = 1;
1159 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1160 $opt = 'ide2' if $opt eq 'cdrom';
1162 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1163 "-delete $opt' at the same time" })
1164 if defined($param->{$opt});
1166 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1167 "-revert $opt' at the same time" })
1170 if (!PVE
::QemuServer
::option_exists
($opt)) {
1171 raise_param_exc
({ delete => "unknown option '$opt'" });
1177 my $repl_conf = PVE
::ReplicationConfig-
>new();
1178 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1179 my $check_replication = sub {
1181 return if !$is_replicated;
1182 my $volid = $drive->{file
};
1183 return if !$volid || !($drive->{replicate
}//1);
1184 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1186 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1187 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1188 if !defined($storeid);
1190 return if defined($volname) && $volname eq 'cloudinit';
1193 if ($volid =~ $NEW_DISK_RE) {
1195 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1197 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1199 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1200 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1201 return if $scfg->{shared
};
1202 die "cannot add non-replicatable volume to a replicated VM\n";
1205 foreach my $opt (keys %$param) {
1206 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1207 # cleanup drive path
1208 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1209 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1210 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1211 $check_replication->($drive);
1212 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1213 } elsif ($opt =~ m/^net(\d+)$/) {
1215 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1216 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1217 } elsif ($opt eq 'vmgenid') {
1218 if ($param->{$opt} eq '1') {
1219 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1221 } elsif ($opt eq 'hookscript') {
1222 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1223 raise_param_exc
({ $opt => $@ }) if $@;
1227 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1229 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1231 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1233 my $updatefn = sub {
1235 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1237 die "checksum missmatch (file change by other user?)\n"
1238 if $digest && $digest ne $conf->{digest
};
1240 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1242 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1243 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1244 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1245 delete $conf->{lock}; # for check lock check, not written out
1246 push @delete, 'lock'; # this is the real deal to write it out
1248 push @delete, 'runningmachine' if $conf->{runningmachine
};
1249 push @delete, 'runningcpu' if $conf->{runningcpu
};
1252 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1254 foreach my $opt (keys %$revert) {
1255 if (defined($conf->{$opt})) {
1256 $param->{$opt} = $conf->{$opt};
1257 } elsif (defined($conf->{pending
}->{$opt})) {
1262 if ($param->{memory
} || defined($param->{balloon
})) {
1263 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1264 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1266 die "balloon value too large (must be smaller than assigned memory)\n"
1267 if $balloon && $balloon > $maxmem;
1270 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1274 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1276 # write updates to pending section
1278 my $modified = {}; # record what $option we modify
1281 if (my $boot = $conf->{boot
}) {
1282 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1283 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1285 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1287 my $check_drive_perms = sub {
1288 my ($opt, $val) = @_;
1289 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1290 # FIXME: cloudinit: CDROM or Disk?
1291 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1292 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1294 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1298 foreach my $opt (@delete) {
1299 $modified->{$opt} = 1;
1300 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1302 # value of what we want to delete, independent if pending or not
1303 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1304 if (!defined($val)) {
1305 warn "cannot delete '$opt' - not set in current configuration!\n";
1306 $modified->{$opt} = 0;
1309 my $is_pending_val = defined($conf->{pending
}->{$opt});
1310 delete $conf->{pending
}->{$opt};
1312 # remove from bootorder if necessary
1313 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1314 @bootorder = grep {$_ ne $opt} @bootorder;
1315 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1316 $modified->{boot
} = 1;
1319 if ($opt =~ m/^unused/) {
1320 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1321 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1322 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1323 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1324 delete $conf->{$opt};
1325 PVE
::QemuConfig-
>write_config($vmid, $conf);
1327 } elsif ($opt eq 'vmstate') {
1328 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1329 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1330 delete $conf->{$opt};
1331 PVE
::QemuConfig-
>write_config($vmid, $conf);
1333 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1334 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1335 $check_drive_perms->($opt, $val);
1336 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1338 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1339 PVE
::QemuConfig-
>write_config($vmid, $conf);
1340 } elsif ($opt =~ m/^serial\d+$/) {
1341 if ($val eq 'socket') {
1342 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1343 } elsif ($authuser ne 'root@pam') {
1344 die "only root can delete '$opt' config for real devices\n";
1346 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1347 PVE
::QemuConfig-
>write_config($vmid, $conf);
1348 } elsif ($opt =~ m/^usb\d+$/) {
1349 if ($val =~ m/spice/) {
1350 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1351 } elsif ($authuser ne 'root@pam') {
1352 die "only root can delete '$opt' config for real devices\n";
1354 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1355 PVE
::QemuConfig-
>write_config($vmid, $conf);
1357 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1358 PVE
::QemuConfig-
>write_config($vmid, $conf);
1362 foreach my $opt (keys %$param) { # add/change
1363 $modified->{$opt} = 1;
1364 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1365 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1367 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1369 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1371 if ($conf->{$opt}) {
1372 $check_drive_perms->($opt, $conf->{$opt});
1376 $check_drive_perms->($opt, $param->{$opt});
1377 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1378 if defined($conf->{pending
}->{$opt});
1380 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1382 # default legacy boot order implies all cdroms anyway
1384 # append new CD drives to bootorder to mark them bootable
1385 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1386 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1) && !grep(/^$opt$/, @bootorder)) {
1387 push @bootorder, $opt;
1388 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1389 $modified->{boot
} = 1;
1392 } elsif ($opt =~ m/^serial\d+/) {
1393 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1394 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1395 } elsif ($authuser ne 'root@pam') {
1396 die "only root can modify '$opt' config for real devices\n";
1398 $conf->{pending
}->{$opt} = $param->{$opt};
1399 } elsif ($opt =~ m/^usb\d+/) {
1400 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1401 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1402 } elsif ($authuser ne 'root@pam') {
1403 die "only root can modify '$opt' config for real devices\n";
1405 $conf->{pending
}->{$opt} = $param->{$opt};
1407 $conf->{pending
}->{$opt} = $param->{$opt};
1409 if ($opt eq 'boot') {
1410 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1411 if ($new_bootcfg->{order
}) {
1412 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1413 for my $dev (@devs) {
1414 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1415 my $deleted = grep {$_ eq $dev} @delete;
1416 die "invalid bootorder: device '$dev' does not exist'\n"
1417 if !$exists || $deleted;
1420 # remove legacy boot order settings if new one set
1421 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1422 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1423 if $conf->{bootdisk
};
1427 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1428 PVE
::QemuConfig-
>write_config($vmid, $conf);
1431 # remove pending changes when nothing changed
1432 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1433 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1434 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1436 return if !scalar(keys %{$conf->{pending
}});
1438 my $running = PVE
::QemuServer
::check_running
($vmid);
1440 # apply pending changes
1442 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1446 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1448 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $errors);
1450 raise_param_exc
($errors) if scalar(keys %$errors);
1459 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1461 if ($background_delay) {
1463 # Note: It would be better to do that in the Event based HTTPServer
1464 # to avoid blocking call to sleep.
1466 my $end_time = time() + $background_delay;
1468 my $task = PVE
::Tools
::upid_decode
($upid);
1471 while (time() < $end_time) {
1472 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1474 sleep(1); # this gets interrupted when child process ends
1478 my $status = PVE
::Tools
::upid_read_status
($upid);
1479 return if !PVE
::Tools
::upid_status_is_error
($status);
1488 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1491 my $vm_config_perm_list = [
1496 'VM.Config.Network',
1498 'VM.Config.Options',
1499 'VM.Config.Cloudinit',
1502 __PACKAGE__-
>register_method({
1503 name
=> 'update_vm_async',
1504 path
=> '{vmid}/config',
1508 description
=> "Set virtual machine options (asynchrounous API).",
1510 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1513 additionalProperties
=> 0,
1514 properties
=> PVE
::QemuServer
::json_config_properties
(
1516 node
=> get_standard_option
('pve-node'),
1517 vmid
=> get_standard_option
('pve-vmid'),
1518 skiplock
=> get_standard_option
('skiplock'),
1520 type
=> 'string', format
=> 'pve-configid-list',
1521 description
=> "A list of settings you want to delete.",
1525 type
=> 'string', format
=> 'pve-configid-list',
1526 description
=> "Revert a pending change.",
1531 description
=> $opt_force_description,
1533 requires
=> 'delete',
1537 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1541 background_delay
=> {
1543 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1554 code
=> $update_vm_api,
1557 __PACKAGE__-
>register_method({
1558 name
=> 'update_vm',
1559 path
=> '{vmid}/config',
1563 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1565 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1568 additionalProperties
=> 0,
1569 properties
=> PVE
::QemuServer
::json_config_properties
(
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1573 skiplock
=> get_standard_option
('skiplock'),
1575 type
=> 'string', format
=> 'pve-configid-list',
1576 description
=> "A list of settings you want to delete.",
1580 type
=> 'string', format
=> 'pve-configid-list',
1581 description
=> "Revert a pending change.",
1586 description
=> $opt_force_description,
1588 requires
=> 'delete',
1592 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1598 returns
=> { type
=> 'null' },
1601 &$update_vm_api($param, 1);
1606 __PACKAGE__-
>register_method({
1607 name
=> 'destroy_vm',
1612 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1613 ." and firewall rules",
1615 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1618 additionalProperties
=> 0,
1620 node
=> get_standard_option
('pve-node'),
1621 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1622 skiplock
=> get_standard_option
('skiplock'),
1625 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1628 'destroy-unreferenced-disks' => {
1630 description
=> "If set, destroy additionally all disks not referenced in the config"
1631 ." but with a matching VMID from all enabled storages.",
1643 my $rpcenv = PVE
::RPCEnvironment
::get
();
1644 my $authuser = $rpcenv->get_user();
1645 my $vmid = $param->{vmid
};
1647 my $skiplock = $param->{skiplock
};
1648 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1649 if $skiplock && $authuser ne 'root@pam';
1651 my $early_checks = sub {
1653 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1654 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1656 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1658 if (!$param->{purge
}) {
1659 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1661 # don't allow destroy if with replication jobs but no purge param
1662 my $repl_conf = PVE
::ReplicationConfig-
>new();
1663 $repl_conf->check_for_existing_jobs($vmid);
1666 die "VM $vmid is running - destroy failed\n"
1667 if PVE
::QemuServer
::check_running
($vmid);
1677 my $storecfg = PVE
::Storage
::config
();
1679 syslog
('info', "destroy VM $vmid: $upid\n");
1680 PVE
::QemuConfig-
>lock_config($vmid, sub {
1681 # repeat, config might have changed
1682 my $ha_managed = $early_checks->();
1684 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'};
1686 PVE
::QemuServer
::destroy_vm
(
1689 $skiplock, { lock => 'destroyed' },
1690 $purge_unreferenced,
1693 PVE
::AccessControl
::remove_vm_access
($vmid);
1694 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1695 if ($param->{purge
}) {
1696 print "purging VM $vmid from related configurations..\n";
1697 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1698 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1701 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1702 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1706 # only now remove the zombie config, else we can have reuse race
1707 PVE
::QemuConfig-
>destroy_config($vmid);
1711 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1714 __PACKAGE__-
>register_method({
1716 path
=> '{vmid}/unlink',
1720 description
=> "Unlink/delete disk images.",
1722 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1725 additionalProperties
=> 0,
1727 node
=> get_standard_option
('pve-node'),
1728 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1730 type
=> 'string', format
=> 'pve-configid-list',
1731 description
=> "A list of disk IDs you want to delete.",
1735 description
=> $opt_force_description,
1740 returns
=> { type
=> 'null'},
1744 $param->{delete} = extract_param
($param, 'idlist');
1746 __PACKAGE__-
>update_vm($param);
1751 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1752 my $gen_rand_chars = sub {
1755 die "invalid length $length" if $length < 1;
1757 my $min = ord('!'); # first printable ascii
1759 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1760 die "failed to generate random bytes!\n"
1763 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1770 __PACKAGE__-
>register_method({
1772 path
=> '{vmid}/vncproxy',
1776 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1778 description
=> "Creates a TCP VNC proxy connections.",
1780 additionalProperties
=> 0,
1782 node
=> get_standard_option
('pve-node'),
1783 vmid
=> get_standard_option
('pve-vmid'),
1787 description
=> "starts websockify instead of vncproxy",
1789 'generate-password' => {
1793 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1798 additionalProperties
=> 0,
1800 user
=> { type
=> 'string' },
1801 ticket
=> { type
=> 'string' },
1804 description
=> "Returned if requested with 'generate-password' param."
1805 ." Consists of printable ASCII characters ('!' .. '~').",
1808 cert
=> { type
=> 'string' },
1809 port
=> { type
=> 'integer' },
1810 upid
=> { type
=> 'string' },
1816 my $rpcenv = PVE
::RPCEnvironment
::get
();
1818 my $authuser = $rpcenv->get_user();
1820 my $vmid = $param->{vmid
};
1821 my $node = $param->{node
};
1822 my $websocket = $param->{websocket
};
1824 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1828 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1829 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1832 my $authpath = "/vms/$vmid";
1834 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1835 my $password = $ticket;
1836 if ($param->{'generate-password'}) {
1837 $password = $gen_rand_chars->(8);
1840 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1846 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1847 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1848 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1849 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1850 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1852 $family = PVE
::Tools
::get_host_address_family
($node);
1855 my $port = PVE
::Tools
::next_vnc_port
($family);
1862 syslog
('info', "starting vnc proxy $upid\n");
1866 if (defined($serial)) {
1868 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1870 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1871 '-timeout', $timeout, '-authpath', $authpath,
1872 '-perm', 'Sys.Console'];
1874 if ($param->{websocket
}) {
1875 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1876 push @$cmd, '-notls', '-listen', 'localhost';
1879 push @$cmd, '-c', @$remcmd, @$termcmd;
1881 PVE
::Tools
::run_command
($cmd);
1885 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1887 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1889 my $sock = IO
::Socket
::IP-
>new(
1894 GetAddrInfoFlags
=> 0,
1895 ) or die "failed to create socket: $!\n";
1896 # Inside the worker we shouldn't have any previous alarms
1897 # running anyway...:
1899 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1901 accept(my $cli, $sock) or die "connection failed: $!\n";
1904 if (PVE
::Tools
::run_command
($cmd,
1905 output
=> '>&'.fileno($cli),
1906 input
=> '<&'.fileno($cli),
1909 die "Failed to run vncproxy.\n";
1916 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1918 PVE
::Tools
::wait_for_vnc_port
($port);
1927 $res->{password
} = $password if $param->{'generate-password'};
1932 __PACKAGE__-
>register_method({
1933 name
=> 'termproxy',
1934 path
=> '{vmid}/termproxy',
1938 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1940 description
=> "Creates a TCP proxy connections.",
1942 additionalProperties
=> 0,
1944 node
=> get_standard_option
('pve-node'),
1945 vmid
=> get_standard_option
('pve-vmid'),
1949 enum
=> [qw(serial0 serial1 serial2 serial3)],
1950 description
=> "opens a serial terminal (defaults to display)",
1955 additionalProperties
=> 0,
1957 user
=> { type
=> 'string' },
1958 ticket
=> { type
=> 'string' },
1959 port
=> { type
=> 'integer' },
1960 upid
=> { type
=> 'string' },
1966 my $rpcenv = PVE
::RPCEnvironment
::get
();
1968 my $authuser = $rpcenv->get_user();
1970 my $vmid = $param->{vmid
};
1971 my $node = $param->{node
};
1972 my $serial = $param->{serial
};
1974 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1976 if (!defined($serial)) {
1978 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1979 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1983 my $authpath = "/vms/$vmid";
1985 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1990 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1991 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1992 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1993 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1994 push @$remcmd, '--';
1996 $family = PVE
::Tools
::get_host_address_family
($node);
1999 my $port = PVE
::Tools
::next_vnc_port
($family);
2001 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
2002 push @$termcmd, '-iface', $serial if $serial;
2007 syslog
('info', "starting qemu termproxy $upid\n");
2009 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
2010 '--perm', 'VM.Console', '--'];
2011 push @$cmd, @$remcmd, @$termcmd;
2013 PVE
::Tools
::run_command
($cmd);
2016 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
2018 PVE
::Tools
::wait_for_vnc_port
($port);
2028 __PACKAGE__-
>register_method({
2029 name
=> 'vncwebsocket',
2030 path
=> '{vmid}/vncwebsocket',
2033 description
=> "You also need to pass a valid ticket (vncticket).",
2034 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2036 description
=> "Opens a weksocket for VNC traffic.",
2038 additionalProperties
=> 0,
2040 node
=> get_standard_option
('pve-node'),
2041 vmid
=> get_standard_option
('pve-vmid'),
2043 description
=> "Ticket from previous call to vncproxy.",
2048 description
=> "Port number returned by previous vncproxy call.",
2058 port
=> { type
=> 'string' },
2064 my $rpcenv = PVE
::RPCEnvironment
::get
();
2066 my $authuser = $rpcenv->get_user();
2068 my $vmid = $param->{vmid
};
2069 my $node = $param->{node
};
2071 my $authpath = "/vms/$vmid";
2073 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2075 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2077 # Note: VNC ports are acessible from outside, so we do not gain any
2078 # security if we verify that $param->{port} belongs to VM $vmid. This
2079 # check is done by verifying the VNC ticket (inside VNC protocol).
2081 my $port = $param->{port
};
2083 return { port
=> $port };
2086 __PACKAGE__-
>register_method({
2087 name
=> 'spiceproxy',
2088 path
=> '{vmid}/spiceproxy',
2093 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2095 description
=> "Returns a SPICE configuration to connect to the VM.",
2097 additionalProperties
=> 0,
2099 node
=> get_standard_option
('pve-node'),
2100 vmid
=> get_standard_option
('pve-vmid'),
2101 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2104 returns
=> get_standard_option
('remote-viewer-config'),
2108 my $rpcenv = PVE
::RPCEnvironment
::get
();
2110 my $authuser = $rpcenv->get_user();
2112 my $vmid = $param->{vmid
};
2113 my $node = $param->{node
};
2114 my $proxy = $param->{proxy
};
2116 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2117 my $title = "VM $vmid";
2118 $title .= " - ". $conf->{name
} if $conf->{name
};
2120 my $port = PVE
::QemuServer
::spice_port
($vmid);
2122 my ($ticket, undef, $remote_viewer_config) =
2123 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2125 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2126 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2128 return $remote_viewer_config;
2131 __PACKAGE__-
>register_method({
2133 path
=> '{vmid}/status',
2136 description
=> "Directory index",
2141 additionalProperties
=> 0,
2143 node
=> get_standard_option
('pve-node'),
2144 vmid
=> get_standard_option
('pve-vmid'),
2152 subdir
=> { type
=> 'string' },
2155 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2161 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2164 { subdir
=> 'current' },
2165 { subdir
=> 'start' },
2166 { subdir
=> 'stop' },
2167 { subdir
=> 'reset' },
2168 { subdir
=> 'shutdown' },
2169 { subdir
=> 'suspend' },
2170 { subdir
=> 'reboot' },
2176 __PACKAGE__-
>register_method({
2177 name
=> 'vm_status',
2178 path
=> '{vmid}/status/current',
2181 protected
=> 1, # qemu pid files are only readable by root
2182 description
=> "Get virtual machine status.",
2184 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2187 additionalProperties
=> 0,
2189 node
=> get_standard_option
('pve-node'),
2190 vmid
=> get_standard_option
('pve-vmid'),
2196 %$PVE::QemuServer
::vmstatus_return_properties
,
2198 description
=> "HA manager service status.",
2202 description
=> "Qemu VGA configuration supports spice.",
2207 description
=> "Qemu GuestAgent enabled in config.",
2217 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2219 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2220 my $status = $vmstatus->{$param->{vmid
}};
2222 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2224 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2225 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2230 __PACKAGE__-
>register_method({
2232 path
=> '{vmid}/status/start',
2236 description
=> "Start virtual machine.",
2238 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2241 additionalProperties
=> 0,
2243 node
=> get_standard_option
('pve-node'),
2244 vmid
=> get_standard_option
('pve-vmid',
2245 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2246 skiplock
=> get_standard_option
('skiplock'),
2247 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2248 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2251 enum
=> ['secure', 'insecure'],
2252 description
=> "Migration traffic is encrypted using an SSH " .
2253 "tunnel by default. On secure, completely private networks " .
2254 "this can be disabled to increase performance.",
2257 migration_network
=> {
2258 type
=> 'string', format
=> 'CIDR',
2259 description
=> "CIDR of the (sub) network that is used for migration.",
2262 machine
=> get_standard_option
('pve-qemu-machine'),
2264 description
=> "Override QEMU's -cpu argument with the given string.",
2268 targetstorage
=> get_standard_option
('pve-targetstorage'),
2270 description
=> "Wait maximal timeout seconds.",
2273 default => 'max(30, vm memory in GiB)',
2284 my $rpcenv = PVE
::RPCEnvironment
::get
();
2285 my $authuser = $rpcenv->get_user();
2287 my $node = extract_param
($param, 'node');
2288 my $vmid = extract_param
($param, 'vmid');
2289 my $timeout = extract_param
($param, 'timeout');
2291 my $machine = extract_param
($param, 'machine');
2292 my $force_cpu = extract_param
($param, 'force-cpu');
2294 my $get_root_param = sub {
2295 my $value = extract_param
($param, $_[0]);
2296 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2297 if $value && $authuser ne 'root@pam';
2301 my $stateuri = $get_root_param->('stateuri');
2302 my $skiplock = $get_root_param->('skiplock');
2303 my $migratedfrom = $get_root_param->('migratedfrom');
2304 my $migration_type = $get_root_param->('migration_type');
2305 my $migration_network = $get_root_param->('migration_network');
2306 my $targetstorage = $get_root_param->('targetstorage');
2310 if ($targetstorage) {
2311 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2313 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2314 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2318 # read spice ticket from STDIN
2320 my $nbd_protocol_version = 0;
2321 my $replicated_volumes = {};
2322 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2323 while (defined(my $line = <STDIN
>)) {
2325 if ($line =~ m/^spice_ticket: (.+)$/) {
2327 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2328 $nbd_protocol_version = $1;
2329 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2330 $replicated_volumes->{$1} = 1;
2332 # fallback for old source node
2333 $spice_ticket = $line;
2338 PVE
::Cluster
::check_cfs_quorum
();
2340 my $storecfg = PVE
::Storage
::config
();
2342 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2346 print "Requesting HA start for VM $vmid\n";
2348 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2349 PVE
::Tools
::run_command
($cmd);
2353 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2360 syslog
('info', "start VM $vmid: $upid\n");
2362 my $migrate_opts = {
2363 migratedfrom
=> $migratedfrom,
2364 spice_ticket
=> $spice_ticket,
2365 network
=> $migration_network,
2366 type
=> $migration_type,
2367 storagemap
=> $storagemap,
2368 nbd_proto_version
=> $nbd_protocol_version,
2369 replicated_volumes
=> $replicated_volumes,
2373 statefile
=> $stateuri,
2374 skiplock
=> $skiplock,
2375 forcemachine
=> $machine,
2376 timeout
=> $timeout,
2377 forcecpu
=> $force_cpu,
2380 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2384 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2388 __PACKAGE__-
>register_method({
2390 path
=> '{vmid}/status/stop',
2394 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2395 "is akin to pulling the power plug of a running computer and may damage the VM data",
2397 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2400 additionalProperties
=> 0,
2402 node
=> get_standard_option
('pve-node'),
2403 vmid
=> get_standard_option
('pve-vmid',
2404 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2405 skiplock
=> get_standard_option
('skiplock'),
2406 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2408 description
=> "Wait maximal timeout seconds.",
2414 description
=> "Do not deactivate storage volumes.",
2427 my $rpcenv = PVE
::RPCEnvironment
::get
();
2428 my $authuser = $rpcenv->get_user();
2430 my $node = extract_param
($param, 'node');
2431 my $vmid = extract_param
($param, 'vmid');
2433 my $skiplock = extract_param
($param, 'skiplock');
2434 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2435 if $skiplock && $authuser ne 'root@pam';
2437 my $keepActive = extract_param
($param, 'keepActive');
2438 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2439 if $keepActive && $authuser ne 'root@pam';
2441 my $migratedfrom = extract_param
($param, 'migratedfrom');
2442 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2443 if $migratedfrom && $authuser ne 'root@pam';
2446 my $storecfg = PVE
::Storage
::config
();
2448 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2453 print "Requesting HA stop for VM $vmid\n";
2455 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2456 PVE
::Tools
::run_command
($cmd);
2460 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2466 syslog
('info', "stop VM $vmid: $upid\n");
2468 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2469 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2473 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2477 __PACKAGE__-
>register_method({
2479 path
=> '{vmid}/status/reset',
2483 description
=> "Reset virtual machine.",
2485 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2488 additionalProperties
=> 0,
2490 node
=> get_standard_option
('pve-node'),
2491 vmid
=> get_standard_option
('pve-vmid',
2492 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2493 skiplock
=> get_standard_option
('skiplock'),
2502 my $rpcenv = PVE
::RPCEnvironment
::get
();
2504 my $authuser = $rpcenv->get_user();
2506 my $node = extract_param
($param, 'node');
2508 my $vmid = extract_param
($param, 'vmid');
2510 my $skiplock = extract_param
($param, 'skiplock');
2511 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2512 if $skiplock && $authuser ne 'root@pam';
2514 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2519 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2524 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2527 __PACKAGE__-
>register_method({
2528 name
=> 'vm_shutdown',
2529 path
=> '{vmid}/status/shutdown',
2533 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2534 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2536 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2539 additionalProperties
=> 0,
2541 node
=> get_standard_option
('pve-node'),
2542 vmid
=> get_standard_option
('pve-vmid',
2543 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2544 skiplock
=> get_standard_option
('skiplock'),
2546 description
=> "Wait maximal timeout seconds.",
2552 description
=> "Make sure the VM stops.",
2558 description
=> "Do not deactivate storage volumes.",
2571 my $rpcenv = PVE
::RPCEnvironment
::get
();
2572 my $authuser = $rpcenv->get_user();
2574 my $node = extract_param
($param, 'node');
2575 my $vmid = extract_param
($param, 'vmid');
2577 my $skiplock = extract_param
($param, 'skiplock');
2578 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2579 if $skiplock && $authuser ne 'root@pam';
2581 my $keepActive = extract_param
($param, 'keepActive');
2582 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2583 if $keepActive && $authuser ne 'root@pam';
2585 my $storecfg = PVE
::Storage
::config
();
2589 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2590 # otherwise, we will infer a shutdown command, but run into the timeout,
2591 # then when the vm is resumed, it will instantly shutdown
2593 # checking the qmp status here to get feedback to the gui/cli/api
2594 # and the status query should not take too long
2595 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2596 if ($param->{forceStop
}) {
2597 warn "VM is paused - stop instead of shutdown\n";
2600 die "VM is paused - cannot shutdown\n";
2604 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2606 my $timeout = $param->{timeout
} // 60;
2610 print "Requesting HA stop for VM $vmid\n";
2612 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2613 PVE
::Tools
::run_command
($cmd);
2617 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2624 syslog
('info', "shutdown VM $vmid: $upid\n");
2626 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2627 $shutdown, $param->{forceStop
}, $keepActive);
2631 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2635 __PACKAGE__-
>register_method({
2636 name
=> 'vm_reboot',
2637 path
=> '{vmid}/status/reboot',
2641 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2643 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2646 additionalProperties
=> 0,
2648 node
=> get_standard_option
('pve-node'),
2649 vmid
=> get_standard_option
('pve-vmid',
2650 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2652 description
=> "Wait maximal timeout seconds for the shutdown.",
2665 my $rpcenv = PVE
::RPCEnvironment
::get
();
2666 my $authuser = $rpcenv->get_user();
2668 my $node = extract_param
($param, 'node');
2669 my $vmid = extract_param
($param, 'vmid');
2671 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2673 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2678 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2679 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2683 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2686 __PACKAGE__-
>register_method({
2687 name
=> 'vm_suspend',
2688 path
=> '{vmid}/status/suspend',
2692 description
=> "Suspend virtual machine.",
2694 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2695 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2696 " on the storage for the vmstate.",
2697 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2700 additionalProperties
=> 0,
2702 node
=> get_standard_option
('pve-node'),
2703 vmid
=> get_standard_option
('pve-vmid',
2704 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2705 skiplock
=> get_standard_option
('skiplock'),
2710 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2712 statestorage
=> get_standard_option
('pve-storage-id', {
2713 description
=> "The storage for the VM state",
2714 requires
=> 'todisk',
2716 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2726 my $rpcenv = PVE
::RPCEnvironment
::get
();
2727 my $authuser = $rpcenv->get_user();
2729 my $node = extract_param
($param, 'node');
2730 my $vmid = extract_param
($param, 'vmid');
2732 my $todisk = extract_param
($param, 'todisk') // 0;
2734 my $statestorage = extract_param
($param, 'statestorage');
2736 my $skiplock = extract_param
($param, 'skiplock');
2737 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2738 if $skiplock && $authuser ne 'root@pam';
2740 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2742 die "Cannot suspend HA managed VM to disk\n"
2743 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2745 # early check for storage permission, for better user feedback
2747 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2749 if (!$statestorage) {
2750 # get statestorage from config if none is given
2751 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2752 my $storecfg = PVE
::Storage
::config
();
2753 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2756 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2762 syslog
('info', "suspend VM $vmid: $upid\n");
2764 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2769 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2770 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2773 __PACKAGE__-
>register_method({
2774 name
=> 'vm_resume',
2775 path
=> '{vmid}/status/resume',
2779 description
=> "Resume virtual machine.",
2781 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2784 additionalProperties
=> 0,
2786 node
=> get_standard_option
('pve-node'),
2787 vmid
=> get_standard_option
('pve-vmid',
2788 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2789 skiplock
=> get_standard_option
('skiplock'),
2790 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2800 my $rpcenv = PVE
::RPCEnvironment
::get
();
2802 my $authuser = $rpcenv->get_user();
2804 my $node = extract_param
($param, 'node');
2806 my $vmid = extract_param
($param, 'vmid');
2808 my $skiplock = extract_param
($param, 'skiplock');
2809 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2810 if $skiplock && $authuser ne 'root@pam';
2812 my $nocheck = extract_param
($param, 'nocheck');
2813 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2814 if $nocheck && $authuser ne 'root@pam';
2816 my $to_disk_suspended;
2818 PVE
::QemuConfig-
>lock_config($vmid, sub {
2819 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2820 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2824 die "VM $vmid not running\n"
2825 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2830 syslog
('info', "resume VM $vmid: $upid\n");
2832 if (!$to_disk_suspended) {
2833 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2835 my $storecfg = PVE
::Storage
::config
();
2836 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2842 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2845 __PACKAGE__-
>register_method({
2846 name
=> 'vm_sendkey',
2847 path
=> '{vmid}/sendkey',
2851 description
=> "Send key event to virtual machine.",
2853 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2856 additionalProperties
=> 0,
2858 node
=> get_standard_option
('pve-node'),
2859 vmid
=> get_standard_option
('pve-vmid',
2860 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2861 skiplock
=> get_standard_option
('skiplock'),
2863 description
=> "The key (qemu monitor encoding).",
2868 returns
=> { type
=> 'null'},
2872 my $rpcenv = PVE
::RPCEnvironment
::get
();
2874 my $authuser = $rpcenv->get_user();
2876 my $node = extract_param
($param, 'node');
2878 my $vmid = extract_param
($param, 'vmid');
2880 my $skiplock = extract_param
($param, 'skiplock');
2881 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2882 if $skiplock && $authuser ne 'root@pam';
2884 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2889 __PACKAGE__-
>register_method({
2890 name
=> 'vm_feature',
2891 path
=> '{vmid}/feature',
2895 description
=> "Check if feature for virtual machine is available.",
2897 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2900 additionalProperties
=> 0,
2902 node
=> get_standard_option
('pve-node'),
2903 vmid
=> get_standard_option
('pve-vmid'),
2905 description
=> "Feature to check.",
2907 enum
=> [ 'snapshot', 'clone', 'copy' ],
2909 snapname
=> get_standard_option
('pve-snapshot-name', {
2917 hasFeature
=> { type
=> 'boolean' },
2920 items
=> { type
=> 'string' },
2927 my $node = extract_param
($param, 'node');
2929 my $vmid = extract_param
($param, 'vmid');
2931 my $snapname = extract_param
($param, 'snapname');
2933 my $feature = extract_param
($param, 'feature');
2935 my $running = PVE
::QemuServer
::check_running
($vmid);
2937 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2940 my $snap = $conf->{snapshots
}->{$snapname};
2941 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2944 my $storecfg = PVE
::Storage
::config
();
2946 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2947 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2950 hasFeature
=> $hasFeature,
2951 nodes
=> [ keys %$nodelist ],
2955 __PACKAGE__-
>register_method({
2957 path
=> '{vmid}/clone',
2961 description
=> "Create a copy of virtual machine/template.",
2963 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2964 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2965 "'Datastore.AllocateSpace' on any used storage.",
2968 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2970 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2971 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2976 additionalProperties
=> 0,
2978 node
=> get_standard_option
('pve-node'),
2979 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2980 newid
=> get_standard_option
('pve-vmid', {
2981 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2982 description
=> 'VMID for the clone.' }),
2985 type
=> 'string', format
=> 'dns-name',
2986 description
=> "Set a name for the new VM.",
2991 description
=> "Description for the new VM.",
2995 type
=> 'string', format
=> 'pve-poolid',
2996 description
=> "Add the new VM to the specified pool.",
2998 snapname
=> get_standard_option
('pve-snapshot-name', {
3001 storage
=> get_standard_option
('pve-storage-id', {
3002 description
=> "Target storage for full clone.",
3006 description
=> "Target format for file storage. Only valid for full clone.",
3009 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3014 description
=> "Create a full copy of all disks. This is always done when " .
3015 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3017 target
=> get_standard_option
('pve-node', {
3018 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3022 description
=> "Override I/O bandwidth limit (in KiB/s).",
3026 default => 'clone limit from datacenter or storage config',
3036 my $rpcenv = PVE
::RPCEnvironment
::get
();
3037 my $authuser = $rpcenv->get_user();
3039 my $node = extract_param
($param, 'node');
3040 my $vmid = extract_param
($param, 'vmid');
3041 my $newid = extract_param
($param, 'newid');
3042 my $pool = extract_param
($param, 'pool');
3043 $rpcenv->check_pool_exist($pool) if defined($pool);
3045 my $snapname = extract_param
($param, 'snapname');
3046 my $storage = extract_param
($param, 'storage');
3047 my $format = extract_param
($param, 'format');
3048 my $target = extract_param
($param, 'target');
3050 my $localnode = PVE
::INotify
::nodename
();
3052 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3056 PVE
::Cluster
::check_node_exists
($target) if $target;
3058 my $storecfg = PVE
::Storage
::config
();
3061 # check if storage is enabled on local node
3062 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3064 # check if storage is available on target node
3065 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3066 # clone only works if target storage is shared
3067 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3068 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3072 PVE
::Cluster
::check_cfs_quorum
();
3074 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3077 # do all tests after lock but before forking worker - if possible
3079 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3080 PVE
::QemuConfig-
>check_lock($conf);
3082 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3083 die "unexpected state change\n" if $verify_running != $running;
3085 die "snapshot '$snapname' does not exist\n"
3086 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3088 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3090 die "parameter 'storage' not allowed for linked clones\n"
3091 if defined($storage) && !$full;
3093 die "parameter 'format' not allowed for linked clones\n"
3094 if defined($format) && !$full;
3096 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3098 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3100 die "can't clone VM to node '$target' (VM uses local storage)\n"
3101 if $target && !$sharedvm;
3103 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3104 die "unable to create VM $newid: config file already exists\n"
3107 my $newconf = { lock => 'clone' };
3112 foreach my $opt (keys %$oldconf) {
3113 my $value = $oldconf->{$opt};
3115 # do not copy snapshot related info
3116 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3117 $opt eq 'vmstate' || $opt eq 'snapstate';
3119 # no need to copy unused images, because VMID(owner) changes anyways
3120 next if $opt =~ m/^unused\d+$/;
3122 # always change MAC! address
3123 if ($opt =~ m/^net(\d+)$/) {
3124 my $net = PVE
::QemuServer
::parse_net
($value);
3125 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3126 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3127 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3128 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3129 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3130 die "unable to parse drive options for '$opt'\n" if !$drive;
3131 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3132 $newconf->{$opt} = $value; # simply copy configuration
3134 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3135 die "Full clone feature is not supported for drive '$opt'\n"
3136 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3137 $fullclone->{$opt} = 1;
3139 # not full means clone instead of copy
3140 die "Linked clone feature is not supported for drive '$opt'\n"
3141 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3143 $drives->{$opt} = $drive;
3144 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3145 push @$vollist, $drive->{file
};
3148 # copy everything else
3149 $newconf->{$opt} = $value;
3153 # auto generate a new uuid
3154 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3155 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3156 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3157 # auto generate a new vmgenid only if the option was set for template
3158 if ($newconf->{vmgenid
}) {
3159 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3162 delete $newconf->{template
};
3164 if ($param->{name
}) {
3165 $newconf->{name
} = $param->{name
};
3167 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3170 if ($param->{description
}) {
3171 $newconf->{description
} = $param->{description
};
3174 # create empty/temp config - this fails if VM already exists on other node
3175 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3176 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3181 my $newvollist = [];
3188 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3190 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3192 my $bwlimit = extract_param
($param, 'bwlimit');
3194 my $total_jobs = scalar(keys %{$drives});
3197 foreach my $opt (sort keys %$drives) {
3198 my $drive = $drives->{$opt};
3199 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3200 my $completion = $skipcomplete ?
'skip' : 'complete';
3202 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3203 my $storage_list = [ $src_sid ];
3204 push @$storage_list, $storage if defined($storage);
3205 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3207 my $newdrive = PVE
::QemuServer
::clone_disk
(
3226 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3228 PVE
::QemuConfig-
>write_config($newid, $newconf);
3232 delete $newconf->{lock};
3234 # do not write pending changes
3235 if (my @changes = keys %{$newconf->{pending
}}) {
3236 my $pending = join(',', @changes);
3237 warn "found pending changes for '$pending', discarding for clone\n";
3238 delete $newconf->{pending
};
3241 PVE
::QemuConfig-
>write_config($newid, $newconf);
3244 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3245 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3246 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3248 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3249 die "Failed to move config to node '$target' - rename failed: $!\n"
3250 if !rename($conffile, $newconffile);
3253 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3256 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3257 sleep 1; # some storage like rbd need to wait before release volume - really?
3259 foreach my $volid (@$newvollist) {
3260 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3264 PVE
::Firewall
::remove_vmfw_conf
($newid);
3266 unlink $conffile; # avoid races -> last thing before die
3268 die "clone failed: $err";
3274 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3276 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3279 # Aquire exclusive lock lock for $newid
3280 my $lock_target_vm = sub {
3281 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3284 # exclusive lock if VM is running - else shared lock is enough;
3286 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3288 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3292 __PACKAGE__-
>register_method({
3293 name
=> 'move_vm_disk',
3294 path
=> '{vmid}/move_disk',
3298 description
=> "Move volume to different storage or to a different VM.",
3300 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3301 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3302 "a disk to another VM, you need the permissions on the target VM as well.",
3303 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3306 additionalProperties
=> 0,
3308 node
=> get_standard_option
('pve-node'),
3309 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3310 'target-vmid' => get_standard_option
('pve-vmid', {
3311 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3316 description
=> "The disk you want to move.",
3317 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3319 storage
=> get_standard_option
('pve-storage-id', {
3320 description
=> "Target storage.",
3321 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3326 description
=> "Target Format.",
3327 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3332 description
=> "Delete the original disk after successful copy. By default the " .
3333 "original disk is kept as unused disk.",
3339 description
=> 'Prevent changes if current configuration file has different SHA1 " .
3340 "digest. This can be used to prevent concurrent modifications.',
3345 description
=> "Override I/O bandwidth limit (in KiB/s).",
3349 default => 'move limit from datacenter or storage config',
3353 description
=> "The config key the disk will be moved to on the target VM " .
3354 "(for example, ide0 or scsi1). Default is the source disk key.",
3355 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3358 'target-digest' => {
3360 description
=> 'Prevent changes if current configuration file of the target VM has " .
3361 "a different SHA1 digest. This can be used to prevent concurrent modifications.',
3369 description
=> "the task ID.",
3374 my $rpcenv = PVE
::RPCEnvironment
::get
();
3375 my $authuser = $rpcenv->get_user();
3377 my $node = extract_param
($param, 'node');
3378 my $vmid = extract_param
($param, 'vmid');
3379 my $target_vmid = extract_param
($param, 'target-vmid');
3380 my $digest = extract_param
($param, 'digest');
3381 my $target_digest = extract_param
($param, 'target-digest');
3382 my $disk = extract_param
($param, 'disk');
3383 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3384 my $storeid = extract_param
($param, 'storage');
3385 my $format = extract_param
($param, 'format');
3387 my $storecfg = PVE
::Storage
::config
();
3389 my $move_updatefn = sub {
3390 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3391 PVE
::QemuConfig-
>check_lock($conf);
3393 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3395 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3397 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3399 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3400 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3402 my $old_volid = $drive->{file
};
3404 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3405 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3409 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3410 (!$format || !$oldfmt || $oldfmt eq $format);
3412 # this only checks snapshots because $disk is passed!
3413 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3419 die "you can't move a disk with snapshots and delete the source\n"
3420 if $snapshotted && $param->{delete};
3422 PVE
::Cluster
::log_msg
(
3425 "move disk VM $vmid: move --disk $disk --storage $storeid"
3428 my $running = PVE
::QemuServer
::check_running
($vmid);
3430 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3433 my $newvollist = [];
3439 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3441 warn "moving disk with snapshots, snapshots will not be moved!\n"
3444 my $bwlimit = extract_param
($param, 'bwlimit');
3445 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3447 [$oldstoreid, $storeid],
3451 my $newdrive = PVE
::QemuServer
::clone_disk
(
3469 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3471 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3473 # convert moved disk to base if part of template
3474 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3475 if PVE
::QemuConfig-
>is_template($conf);
3477 PVE
::QemuConfig-
>write_config($vmid, $conf);
3479 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3480 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3481 eval { mon_cmd
($vmid, "guest-fstrim") };
3485 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3486 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3492 foreach my $volid (@$newvollist) {
3493 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3496 die "storage migration failed: $err";
3499 if ($param->{delete}) {
3501 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3502 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3508 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3511 my $load_and_check_reassign_configs = sub {
3512 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
3514 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
3515 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
3517 my $source_node = $vmlist->{$vmid}->{node
};
3518 my $target_node = $vmlist->{$target_vmid}->{node
};
3520 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
3521 if $source_node ne $target_node;
3523 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
3524 PVE
::QemuConfig-
>check_lock($source_conf);
3525 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
3526 PVE
::QemuConfig-
>check_lock($target_conf);
3528 die "Can't move disks from or to template VMs\n"
3529 if ($source_conf->{template
} || $target_conf->{template
});
3532 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
3533 die "VM ${vmid}: $@" if $@;
3536 if ($target_digest) {
3537 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
3538 die "VM ${target_vmid}: $@" if $@;
3541 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
3543 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
3544 if $target_conf->{$target_disk};
3546 my $drive = PVE
::QemuServer
::parse_drive
(
3548 $source_conf->{$disk},
3550 die "failed to parse source disk - $@\n" if !$drive;
3552 my $source_volid = $drive->{file
};
3554 die "disk '${disk}' has no associated volume\n" if !$source_volid;
3555 die "CD drive contents can't be moved to another VM\n"
3556 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3558 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
3559 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
3561 die "Can't move disk used by a snapshot to another VM\n"
3562 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
3563 die "Storage does not support moving of this disk to another VM\n"
3564 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
3565 die "Cannot move disk to another VM while the source VM is running - detach first\n"
3566 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
3568 # now re-parse using target disk slot format
3569 if ($target_disk =~ /^unused\d+$/) {
3570 $drive = PVE
::QemuServer
::parse_drive
(
3575 $drive = PVE
::QemuServer
::parse_drive
(
3577 $source_conf->{$disk},
3580 die "failed to parse source disk for target disk format - $@\n" if !$drive;
3582 my $repl_conf = PVE
::ReplicationConfig-
>new();
3583 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
3584 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3585 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
3586 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
3589 return ($source_conf, $target_conf, $drive);
3594 print STDERR
"$msg\n";
3597 my $disk_reassignfn = sub {
3598 return PVE
::QemuConfig-
>lock_config($vmid, sub {
3599 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
3600 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
3602 my $source_volid = $drive->{file
};
3604 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
3605 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
3607 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3609 my $new_volid = PVE
::Storage
::rename_volume
(
3615 $drive->{file
} = $new_volid;
3617 delete $source_conf->{$disk};
3618 print "removing disk '${disk}' from VM '${vmid}' config\n";
3619 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
3621 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
3623 if ($target_disk =~ /^unused\d+$/) {
3624 $target_conf->{$target_disk} = $drive_string;
3625 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
3630 vmid
=> $target_vmid,
3631 digest
=> $target_digest,
3632 $target_disk => $drive_string,
3638 # remove possible replication snapshots
3639 if (PVE
::Storage
::volume_has_feature
(
3645 PVE
::Replication
::prepare
(
3655 print "Failed to remove replication snapshots on moved disk " .
3656 "'$target_disk'. Manual cleanup could be necessary.\n";
3663 if ($target_vmid && $storeid) {
3664 my $msg = "either set 'storage' or 'target-vmid', but not both";
3665 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3666 } elsif ($target_vmid) {
3667 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
3668 if $authuser ne 'root@pam';
3670 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
3671 if $vmid eq $target_vmid;
3673 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
3674 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
3675 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
3677 return $rpcenv->fork_worker(
3679 "${vmid}-${disk}>${target_vmid}-${target_disk}",
3683 } elsif ($storeid) {
3684 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3686 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
3687 if $disk =~ m/^unused\d+$/;
3688 return PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
3690 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
3691 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3695 my $check_vm_disks_local = sub {
3696 my ($storecfg, $vmconf, $vmid) = @_;
3698 my $local_disks = {};
3700 # add some more information to the disks e.g. cdrom
3701 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3702 my ($volid, $attr) = @_;
3704 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3706 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3707 return if $scfg->{shared
};
3709 # The shared attr here is just a special case where the vdisk
3710 # is marked as shared manually
3711 return if $attr->{shared
};
3712 return if $attr->{cdrom
} and $volid eq "none";
3714 if (exists $local_disks->{$volid}) {
3715 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3717 $local_disks->{$volid} = $attr;
3718 # ensure volid is present in case it's needed
3719 $local_disks->{$volid}->{volid
} = $volid;
3723 return $local_disks;
3726 __PACKAGE__-
>register_method({
3727 name
=> 'migrate_vm_precondition',
3728 path
=> '{vmid}/migrate',
3732 description
=> "Get preconditions for migration.",
3734 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3737 additionalProperties
=> 0,
3739 node
=> get_standard_option
('pve-node'),
3740 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3741 target
=> get_standard_option
('pve-node', {
3742 description
=> "Target node.",
3743 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3751 running
=> { type
=> 'boolean' },
3755 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3757 not_allowed_nodes
=> {
3760 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3764 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3766 local_resources
=> {
3768 description
=> "List local resources e.g. pci, usb"
3775 my $rpcenv = PVE
::RPCEnvironment
::get
();
3777 my $authuser = $rpcenv->get_user();
3779 PVE
::Cluster
::check_cfs_quorum
();
3783 my $vmid = extract_param
($param, 'vmid');
3784 my $target = extract_param
($param, 'target');
3785 my $localnode = PVE
::INotify
::nodename
();
3789 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3790 my $storecfg = PVE
::Storage
::config
();
3793 # try to detect errors early
3794 PVE
::QemuConfig-
>check_lock($vmconf);
3796 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3798 # if vm is not running, return target nodes where local storage is available
3799 # for offline migration
3800 if (!$res->{running
}) {
3801 $res->{allowed_nodes
} = [];
3802 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3803 delete $checked_nodes->{$localnode};
3805 foreach my $node (keys %$checked_nodes) {
3806 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3807 push @{$res->{allowed_nodes
}}, $node;
3811 $res->{not_allowed_nodes
} = $checked_nodes;
3815 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3816 $res->{local_disks
} = [ values %$local_disks ];;
3818 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3820 $res->{local_resources
} = $local_resources;
3827 __PACKAGE__-
>register_method({
3828 name
=> 'migrate_vm',
3829 path
=> '{vmid}/migrate',
3833 description
=> "Migrate virtual machine. Creates a new migration task.",
3835 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3838 additionalProperties
=> 0,
3840 node
=> get_standard_option
('pve-node'),
3841 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3842 target
=> get_standard_option
('pve-node', {
3843 description
=> "Target node.",
3844 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3848 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3853 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3858 enum
=> ['secure', 'insecure'],
3859 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3862 migration_network
=> {
3863 type
=> 'string', format
=> 'CIDR',
3864 description
=> "CIDR of the (sub) network that is used for migration.",
3867 "with-local-disks" => {
3869 description
=> "Enable live storage migration for local disk",
3872 targetstorage
=> get_standard_option
('pve-targetstorage', {
3873 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3876 description
=> "Override I/O bandwidth limit (in KiB/s).",
3880 default => 'migrate limit from datacenter or storage config',
3886 description
=> "the task ID.",
3891 my $rpcenv = PVE
::RPCEnvironment
::get
();
3892 my $authuser = $rpcenv->get_user();
3894 my $target = extract_param
($param, 'target');
3896 my $localnode = PVE
::INotify
::nodename
();
3897 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3899 PVE
::Cluster
::check_cfs_quorum
();
3901 PVE
::Cluster
::check_node_exists
($target);
3903 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3905 my $vmid = extract_param
($param, 'vmid');
3907 raise_param_exc
({ force
=> "Only root may use this option." })
3908 if $param->{force
} && $authuser ne 'root@pam';
3910 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3911 if $param->{migration_type
} && $authuser ne 'root@pam';
3913 # allow root only until better network permissions are available
3914 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3915 if $param->{migration_network
} && $authuser ne 'root@pam';
3918 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3920 # try to detect errors early
3922 PVE
::QemuConfig-
>check_lock($conf);
3924 if (PVE
::QemuServer
::check_running
($vmid)) {
3925 die "can't migrate running VM without --online\n" if !$param->{online
};
3927 my $repl_conf = PVE
::ReplicationConfig-
>new();
3928 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3929 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3930 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3931 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3932 "target. Use 'force' to override.\n";
3935 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3936 $param->{online
} = 0;
3939 my $storecfg = PVE
::Storage
::config
();
3940 if (my $targetstorage = $param->{targetstorage
}) {
3941 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3942 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3945 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3946 if !defined($storagemap->{identity
});
3948 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3949 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
3952 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
3953 if $storagemap->{default};
3955 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3956 if $storagemap->{identity
};
3958 $param->{storagemap
} = $storagemap;
3960 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3963 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3968 print "Requesting HA migration for VM $vmid to node $target\n";
3970 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3971 PVE
::Tools
::run_command
($cmd);
3975 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3980 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3984 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3987 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3992 __PACKAGE__-
>register_method({
3994 path
=> '{vmid}/monitor',
3998 description
=> "Execute Qemu monitor commands.",
4000 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4001 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4004 additionalProperties
=> 0,
4006 node
=> get_standard_option
('pve-node'),
4007 vmid
=> get_standard_option
('pve-vmid'),
4010 description
=> "The monitor command.",
4014 returns
=> { type
=> 'string'},
4018 my $rpcenv = PVE
::RPCEnvironment
::get
();
4019 my $authuser = $rpcenv->get_user();
4022 my $command = shift;
4023 return $command =~ m/^\s*info(\s+|$)/
4024 || $command =~ m/^\s*help\s*$/;
4027 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4028 if !&$is_ro($param->{command
});
4030 my $vmid = $param->{vmid
};
4032 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4036 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4038 $res = "ERROR: $@" if $@;
4043 __PACKAGE__-
>register_method({
4044 name
=> 'resize_vm',
4045 path
=> '{vmid}/resize',
4049 description
=> "Extend volume size.",
4051 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4054 additionalProperties
=> 0,
4056 node
=> get_standard_option
('pve-node'),
4057 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4058 skiplock
=> get_standard_option
('skiplock'),
4061 description
=> "The disk you want to resize.",
4062 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4066 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4067 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.",
4071 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4077 returns
=> { type
=> 'null'},
4081 my $rpcenv = PVE
::RPCEnvironment
::get
();
4083 my $authuser = $rpcenv->get_user();
4085 my $node = extract_param
($param, 'node');
4087 my $vmid = extract_param
($param, 'vmid');
4089 my $digest = extract_param
($param, 'digest');
4091 my $disk = extract_param
($param, 'disk');
4093 my $sizestr = extract_param
($param, 'size');
4095 my $skiplock = extract_param
($param, 'skiplock');
4096 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4097 if $skiplock && $authuser ne 'root@pam';
4099 my $storecfg = PVE
::Storage
::config
();
4101 my $updatefn = sub {
4103 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4105 die "checksum missmatch (file change by other user?)\n"
4106 if $digest && $digest ne $conf->{digest
};
4107 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4109 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4111 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4113 my (undef, undef, undef, undef, undef, undef, $format) =
4114 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4116 die "can't resize volume: $disk if snapshot exists\n"
4117 if %{$conf->{snapshots
}} && $format eq 'qcow2';
4119 my $volid = $drive->{file
};
4121 die "disk '$disk' has no associated volume\n" if !$volid;
4123 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4125 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4127 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4129 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4130 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4132 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4134 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4135 my ($ext, $newsize, $unit) = ($1, $2, $4);
4138 $newsize = $newsize * 1024;
4139 } elsif ($unit eq 'M') {
4140 $newsize = $newsize * 1024 * 1024;
4141 } elsif ($unit eq 'G') {
4142 $newsize = $newsize * 1024 * 1024 * 1024;
4143 } elsif ($unit eq 'T') {
4144 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4147 $newsize += $size if $ext;
4148 $newsize = int($newsize);
4150 die "shrinking disks is not supported\n" if $newsize < $size;
4152 return if $size == $newsize;
4154 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4156 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4158 $drive->{size
} = $newsize;
4159 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4161 PVE
::QemuConfig-
>write_config($vmid, $conf);
4164 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4168 __PACKAGE__-
>register_method({
4169 name
=> 'snapshot_list',
4170 path
=> '{vmid}/snapshot',
4172 description
=> "List all snapshots.",
4174 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4177 protected
=> 1, # qemu pid files are only readable by root
4179 additionalProperties
=> 0,
4181 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4182 node
=> get_standard_option
('pve-node'),
4191 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4195 description
=> "Snapshot includes RAM.",
4200 description
=> "Snapshot description.",
4204 description
=> "Snapshot creation time",
4206 renderer
=> 'timestamp',
4210 description
=> "Parent snapshot identifier.",
4216 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4221 my $vmid = $param->{vmid
};
4223 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4224 my $snaphash = $conf->{snapshots
} || {};
4228 foreach my $name (keys %$snaphash) {
4229 my $d = $snaphash->{$name};
4232 snaptime
=> $d->{snaptime
} || 0,
4233 vmstate
=> $d->{vmstate
} ?
1 : 0,
4234 description
=> $d->{description
} || '',
4236 $item->{parent
} = $d->{parent
} if $d->{parent
};
4237 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4241 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4244 digest
=> $conf->{digest
},
4245 running
=> $running,
4246 description
=> "You are here!",
4248 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4250 push @$res, $current;
4255 __PACKAGE__-
>register_method({
4257 path
=> '{vmid}/snapshot',
4261 description
=> "Snapshot a VM.",
4263 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4266 additionalProperties
=> 0,
4268 node
=> get_standard_option
('pve-node'),
4269 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4270 snapname
=> get_standard_option
('pve-snapshot-name'),
4274 description
=> "Save the vmstate",
4279 description
=> "A textual description or comment.",
4285 description
=> "the task ID.",
4290 my $rpcenv = PVE
::RPCEnvironment
::get
();
4292 my $authuser = $rpcenv->get_user();
4294 my $node = extract_param
($param, 'node');
4296 my $vmid = extract_param
($param, 'vmid');
4298 my $snapname = extract_param
($param, 'snapname');
4300 die "unable to use snapshot name 'current' (reserved name)\n"
4301 if $snapname eq 'current';
4303 die "unable to use snapshot name 'pending' (reserved name)\n"
4304 if lc($snapname) eq 'pending';
4307 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4308 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4309 $param->{description
});
4312 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4315 __PACKAGE__-
>register_method({
4316 name
=> 'snapshot_cmd_idx',
4317 path
=> '{vmid}/snapshot/{snapname}',
4324 additionalProperties
=> 0,
4326 vmid
=> get_standard_option
('pve-vmid'),
4327 node
=> get_standard_option
('pve-node'),
4328 snapname
=> get_standard_option
('pve-snapshot-name'),
4337 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4344 push @$res, { cmd
=> 'rollback' };
4345 push @$res, { cmd
=> 'config' };
4350 __PACKAGE__-
>register_method({
4351 name
=> 'update_snapshot_config',
4352 path
=> '{vmid}/snapshot/{snapname}/config',
4356 description
=> "Update snapshot metadata.",
4358 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4361 additionalProperties
=> 0,
4363 node
=> get_standard_option
('pve-node'),
4364 vmid
=> get_standard_option
('pve-vmid'),
4365 snapname
=> get_standard_option
('pve-snapshot-name'),
4369 description
=> "A textual description or comment.",
4373 returns
=> { type
=> 'null' },
4377 my $rpcenv = PVE
::RPCEnvironment
::get
();
4379 my $authuser = $rpcenv->get_user();
4381 my $vmid = extract_param
($param, 'vmid');
4383 my $snapname = extract_param
($param, 'snapname');
4385 return if !defined($param->{description
});
4387 my $updatefn = sub {
4389 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4391 PVE
::QemuConfig-
>check_lock($conf);
4393 my $snap = $conf->{snapshots
}->{$snapname};
4395 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4397 $snap->{description
} = $param->{description
} if defined($param->{description
});
4399 PVE
::QemuConfig-
>write_config($vmid, $conf);
4402 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4407 __PACKAGE__-
>register_method({
4408 name
=> 'get_snapshot_config',
4409 path
=> '{vmid}/snapshot/{snapname}/config',
4412 description
=> "Get snapshot configuration",
4414 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4417 additionalProperties
=> 0,
4419 node
=> get_standard_option
('pve-node'),
4420 vmid
=> get_standard_option
('pve-vmid'),
4421 snapname
=> get_standard_option
('pve-snapshot-name'),
4424 returns
=> { type
=> "object" },
4428 my $rpcenv = PVE
::RPCEnvironment
::get
();
4430 my $authuser = $rpcenv->get_user();
4432 my $vmid = extract_param
($param, 'vmid');
4434 my $snapname = extract_param
($param, 'snapname');
4436 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4438 my $snap = $conf->{snapshots
}->{$snapname};
4440 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4445 __PACKAGE__-
>register_method({
4447 path
=> '{vmid}/snapshot/{snapname}/rollback',
4451 description
=> "Rollback VM state to specified snapshot.",
4453 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4456 additionalProperties
=> 0,
4458 node
=> get_standard_option
('pve-node'),
4459 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4460 snapname
=> get_standard_option
('pve-snapshot-name'),
4465 description
=> "the task ID.",
4470 my $rpcenv = PVE
::RPCEnvironment
::get
();
4472 my $authuser = $rpcenv->get_user();
4474 my $node = extract_param
($param, 'node');
4476 my $vmid = extract_param
($param, 'vmid');
4478 my $snapname = extract_param
($param, 'snapname');
4481 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4482 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4486 # hold migration lock, this makes sure that nobody create replication snapshots
4487 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4490 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4493 __PACKAGE__-
>register_method({
4494 name
=> 'delsnapshot',
4495 path
=> '{vmid}/snapshot/{snapname}',
4499 description
=> "Delete a VM snapshot.",
4501 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4504 additionalProperties
=> 0,
4506 node
=> get_standard_option
('pve-node'),
4507 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4508 snapname
=> get_standard_option
('pve-snapshot-name'),
4512 description
=> "For removal from config file, even if removing disk snapshots fails.",
4518 description
=> "the task ID.",
4523 my $rpcenv = PVE
::RPCEnvironment
::get
();
4525 my $authuser = $rpcenv->get_user();
4527 my $node = extract_param
($param, 'node');
4529 my $vmid = extract_param
($param, 'vmid');
4531 my $snapname = extract_param
($param, 'snapname');
4534 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4535 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4538 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4541 __PACKAGE__-
>register_method({
4543 path
=> '{vmid}/template',
4547 description
=> "Create a Template.",
4549 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4550 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4553 additionalProperties
=> 0,
4555 node
=> get_standard_option
('pve-node'),
4556 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4560 description
=> "If you want to convert only 1 disk to base image.",
4561 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4568 description
=> "the task ID.",
4573 my $rpcenv = PVE
::RPCEnvironment
::get
();
4575 my $authuser = $rpcenv->get_user();
4577 my $node = extract_param
($param, 'node');
4579 my $vmid = extract_param
($param, 'vmid');
4581 my $disk = extract_param
($param, 'disk');
4583 my $load_and_check = sub {
4584 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4586 PVE
::QemuConfig-
>check_lock($conf);
4588 die "unable to create template, because VM contains snapshots\n"
4589 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4591 die "you can't convert a template to a template\n"
4592 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4594 die "you can't convert a VM to template if VM is running\n"
4595 if PVE
::QemuServer
::check_running
($vmid);
4600 $load_and_check->();
4603 PVE
::QemuConfig-
>lock_config($vmid, sub {
4604 my $conf = $load_and_check->();
4606 $conf->{template
} = 1;
4607 PVE
::QemuConfig-
>write_config($vmid, $conf);
4609 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4613 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4616 __PACKAGE__-
>register_method({
4617 name
=> 'cloudinit_generated_config_dump',
4618 path
=> '{vmid}/cloudinit/dump',
4621 description
=> "Get automatically generated cloudinit config.",
4623 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4626 additionalProperties
=> 0,
4628 node
=> get_standard_option
('pve-node'),
4629 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4631 description
=> 'Config type.',
4633 enum
=> ['user', 'network', 'meta'],
4643 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4645 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});