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);
1480 die "failed to update VM $vmid: $status\n";
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 = {};
2323 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2324 while (defined(my $line = <STDIN
>)) {
2326 if ($line =~ m/^spice_ticket: (.+)$/) {
2328 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2329 $nbd_protocol_version = $1;
2330 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2331 $replicated_volumes->{$1} = 1;
2332 } elsif ($line =~ m/^tpmstate0: (.*)$/) {
2334 } elsif (!$spice_ticket) {
2335 # fallback for old source node
2336 $spice_ticket = $line;
2338 warn "unknown 'start' parameter on STDIN: '$line'\n";
2343 PVE
::Cluster
::check_cfs_quorum
();
2345 my $storecfg = PVE
::Storage
::config
();
2347 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2351 print "Requesting HA start for VM $vmid\n";
2353 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2354 PVE
::Tools
::run_command
($cmd);
2358 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2365 syslog
('info', "start VM $vmid: $upid\n");
2367 my $migrate_opts = {
2368 migratedfrom
=> $migratedfrom,
2369 spice_ticket
=> $spice_ticket,
2370 network
=> $migration_network,
2371 type
=> $migration_type,
2372 storagemap
=> $storagemap,
2373 nbd_proto_version
=> $nbd_protocol_version,
2374 replicated_volumes
=> $replicated_volumes,
2375 tpmstate_vol
=> $tpmstate_vol,
2379 statefile
=> $stateuri,
2380 skiplock
=> $skiplock,
2381 forcemachine
=> $machine,
2382 timeout
=> $timeout,
2383 forcecpu
=> $force_cpu,
2386 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2390 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2394 __PACKAGE__-
>register_method({
2396 path
=> '{vmid}/status/stop',
2400 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2401 "is akin to pulling the power plug of a running computer and may damage the VM data",
2403 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2406 additionalProperties
=> 0,
2408 node
=> get_standard_option
('pve-node'),
2409 vmid
=> get_standard_option
('pve-vmid',
2410 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2411 skiplock
=> get_standard_option
('skiplock'),
2412 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2414 description
=> "Wait maximal timeout seconds.",
2420 description
=> "Do not deactivate storage volumes.",
2433 my $rpcenv = PVE
::RPCEnvironment
::get
();
2434 my $authuser = $rpcenv->get_user();
2436 my $node = extract_param
($param, 'node');
2437 my $vmid = extract_param
($param, 'vmid');
2439 my $skiplock = extract_param
($param, 'skiplock');
2440 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2441 if $skiplock && $authuser ne 'root@pam';
2443 my $keepActive = extract_param
($param, 'keepActive');
2444 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2445 if $keepActive && $authuser ne 'root@pam';
2447 my $migratedfrom = extract_param
($param, 'migratedfrom');
2448 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2449 if $migratedfrom && $authuser ne 'root@pam';
2452 my $storecfg = PVE
::Storage
::config
();
2454 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2459 print "Requesting HA stop for VM $vmid\n";
2461 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2462 PVE
::Tools
::run_command
($cmd);
2466 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2472 syslog
('info', "stop VM $vmid: $upid\n");
2474 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2475 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2479 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2483 __PACKAGE__-
>register_method({
2485 path
=> '{vmid}/status/reset',
2489 description
=> "Reset virtual machine.",
2491 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2494 additionalProperties
=> 0,
2496 node
=> get_standard_option
('pve-node'),
2497 vmid
=> get_standard_option
('pve-vmid',
2498 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2499 skiplock
=> get_standard_option
('skiplock'),
2508 my $rpcenv = PVE
::RPCEnvironment
::get
();
2510 my $authuser = $rpcenv->get_user();
2512 my $node = extract_param
($param, 'node');
2514 my $vmid = extract_param
($param, 'vmid');
2516 my $skiplock = extract_param
($param, 'skiplock');
2517 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2518 if $skiplock && $authuser ne 'root@pam';
2520 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2525 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2530 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2533 __PACKAGE__-
>register_method({
2534 name
=> 'vm_shutdown',
2535 path
=> '{vmid}/status/shutdown',
2539 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2540 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2542 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2545 additionalProperties
=> 0,
2547 node
=> get_standard_option
('pve-node'),
2548 vmid
=> get_standard_option
('pve-vmid',
2549 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2550 skiplock
=> get_standard_option
('skiplock'),
2552 description
=> "Wait maximal timeout seconds.",
2558 description
=> "Make sure the VM stops.",
2564 description
=> "Do not deactivate storage volumes.",
2577 my $rpcenv = PVE
::RPCEnvironment
::get
();
2578 my $authuser = $rpcenv->get_user();
2580 my $node = extract_param
($param, 'node');
2581 my $vmid = extract_param
($param, 'vmid');
2583 my $skiplock = extract_param
($param, 'skiplock');
2584 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2585 if $skiplock && $authuser ne 'root@pam';
2587 my $keepActive = extract_param
($param, 'keepActive');
2588 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2589 if $keepActive && $authuser ne 'root@pam';
2591 my $storecfg = PVE
::Storage
::config
();
2595 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2596 # otherwise, we will infer a shutdown command, but run into the timeout,
2597 # then when the vm is resumed, it will instantly shutdown
2599 # checking the qmp status here to get feedback to the gui/cli/api
2600 # and the status query should not take too long
2601 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2602 if ($param->{forceStop
}) {
2603 warn "VM is paused - stop instead of shutdown\n";
2606 die "VM is paused - cannot shutdown\n";
2610 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2612 my $timeout = $param->{timeout
} // 60;
2616 print "Requesting HA stop for VM $vmid\n";
2618 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2619 PVE
::Tools
::run_command
($cmd);
2623 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2630 syslog
('info', "shutdown VM $vmid: $upid\n");
2632 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2633 $shutdown, $param->{forceStop
}, $keepActive);
2637 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2641 __PACKAGE__-
>register_method({
2642 name
=> 'vm_reboot',
2643 path
=> '{vmid}/status/reboot',
2647 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2649 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2652 additionalProperties
=> 0,
2654 node
=> get_standard_option
('pve-node'),
2655 vmid
=> get_standard_option
('pve-vmid',
2656 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2658 description
=> "Wait maximal timeout seconds for the shutdown.",
2671 my $rpcenv = PVE
::RPCEnvironment
::get
();
2672 my $authuser = $rpcenv->get_user();
2674 my $node = extract_param
($param, 'node');
2675 my $vmid = extract_param
($param, 'vmid');
2677 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2679 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2684 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2685 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2689 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2692 __PACKAGE__-
>register_method({
2693 name
=> 'vm_suspend',
2694 path
=> '{vmid}/status/suspend',
2698 description
=> "Suspend virtual machine.",
2700 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2701 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2702 " on the storage for the vmstate.",
2703 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2706 additionalProperties
=> 0,
2708 node
=> get_standard_option
('pve-node'),
2709 vmid
=> get_standard_option
('pve-vmid',
2710 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2711 skiplock
=> get_standard_option
('skiplock'),
2716 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2718 statestorage
=> get_standard_option
('pve-storage-id', {
2719 description
=> "The storage for the VM state",
2720 requires
=> 'todisk',
2722 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2732 my $rpcenv = PVE
::RPCEnvironment
::get
();
2733 my $authuser = $rpcenv->get_user();
2735 my $node = extract_param
($param, 'node');
2736 my $vmid = extract_param
($param, 'vmid');
2738 my $todisk = extract_param
($param, 'todisk') // 0;
2740 my $statestorage = extract_param
($param, 'statestorage');
2742 my $skiplock = extract_param
($param, 'skiplock');
2743 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2744 if $skiplock && $authuser ne 'root@pam';
2746 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2748 die "Cannot suspend HA managed VM to disk\n"
2749 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2751 # early check for storage permission, for better user feedback
2753 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2755 if (!$statestorage) {
2756 # get statestorage from config if none is given
2757 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2758 my $storecfg = PVE
::Storage
::config
();
2759 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2762 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2768 syslog
('info', "suspend VM $vmid: $upid\n");
2770 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2775 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2776 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2779 __PACKAGE__-
>register_method({
2780 name
=> 'vm_resume',
2781 path
=> '{vmid}/status/resume',
2785 description
=> "Resume virtual machine.",
2787 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2790 additionalProperties
=> 0,
2792 node
=> get_standard_option
('pve-node'),
2793 vmid
=> get_standard_option
('pve-vmid',
2794 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2795 skiplock
=> get_standard_option
('skiplock'),
2796 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2806 my $rpcenv = PVE
::RPCEnvironment
::get
();
2808 my $authuser = $rpcenv->get_user();
2810 my $node = extract_param
($param, 'node');
2812 my $vmid = extract_param
($param, 'vmid');
2814 my $skiplock = extract_param
($param, 'skiplock');
2815 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2816 if $skiplock && $authuser ne 'root@pam';
2818 my $nocheck = extract_param
($param, 'nocheck');
2819 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2820 if $nocheck && $authuser ne 'root@pam';
2822 my $to_disk_suspended;
2824 PVE
::QemuConfig-
>lock_config($vmid, sub {
2825 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2826 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2830 die "VM $vmid not running\n"
2831 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2836 syslog
('info', "resume VM $vmid: $upid\n");
2838 if (!$to_disk_suspended) {
2839 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2841 my $storecfg = PVE
::Storage
::config
();
2842 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2848 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2851 __PACKAGE__-
>register_method({
2852 name
=> 'vm_sendkey',
2853 path
=> '{vmid}/sendkey',
2857 description
=> "Send key event to virtual machine.",
2859 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2862 additionalProperties
=> 0,
2864 node
=> get_standard_option
('pve-node'),
2865 vmid
=> get_standard_option
('pve-vmid',
2866 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2867 skiplock
=> get_standard_option
('skiplock'),
2869 description
=> "The key (qemu monitor encoding).",
2874 returns
=> { type
=> 'null'},
2878 my $rpcenv = PVE
::RPCEnvironment
::get
();
2880 my $authuser = $rpcenv->get_user();
2882 my $node = extract_param
($param, 'node');
2884 my $vmid = extract_param
($param, 'vmid');
2886 my $skiplock = extract_param
($param, 'skiplock');
2887 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2888 if $skiplock && $authuser ne 'root@pam';
2890 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2895 __PACKAGE__-
>register_method({
2896 name
=> 'vm_feature',
2897 path
=> '{vmid}/feature',
2901 description
=> "Check if feature for virtual machine is available.",
2903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2906 additionalProperties
=> 0,
2908 node
=> get_standard_option
('pve-node'),
2909 vmid
=> get_standard_option
('pve-vmid'),
2911 description
=> "Feature to check.",
2913 enum
=> [ 'snapshot', 'clone', 'copy' ],
2915 snapname
=> get_standard_option
('pve-snapshot-name', {
2923 hasFeature
=> { type
=> 'boolean' },
2926 items
=> { type
=> 'string' },
2933 my $node = extract_param
($param, 'node');
2935 my $vmid = extract_param
($param, 'vmid');
2937 my $snapname = extract_param
($param, 'snapname');
2939 my $feature = extract_param
($param, 'feature');
2941 my $running = PVE
::QemuServer
::check_running
($vmid);
2943 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2946 my $snap = $conf->{snapshots
}->{$snapname};
2947 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2950 my $storecfg = PVE
::Storage
::config
();
2952 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2953 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2956 hasFeature
=> $hasFeature,
2957 nodes
=> [ keys %$nodelist ],
2961 __PACKAGE__-
>register_method({
2963 path
=> '{vmid}/clone',
2967 description
=> "Create a copy of virtual machine/template.",
2969 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2970 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2971 "'Datastore.AllocateSpace' on any used storage.",
2974 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2976 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2977 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2982 additionalProperties
=> 0,
2984 node
=> get_standard_option
('pve-node'),
2985 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2986 newid
=> get_standard_option
('pve-vmid', {
2987 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2988 description
=> 'VMID for the clone.' }),
2991 type
=> 'string', format
=> 'dns-name',
2992 description
=> "Set a name for the new VM.",
2997 description
=> "Description for the new VM.",
3001 type
=> 'string', format
=> 'pve-poolid',
3002 description
=> "Add the new VM to the specified pool.",
3004 snapname
=> get_standard_option
('pve-snapshot-name', {
3007 storage
=> get_standard_option
('pve-storage-id', {
3008 description
=> "Target storage for full clone.",
3012 description
=> "Target format for file storage. Only valid for full clone.",
3015 enum
=> [ 'raw', 'qcow2', 'vmdk'],
3020 description
=> "Create a full copy of all disks. This is always done when " .
3021 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
3023 target
=> get_standard_option
('pve-node', {
3024 description
=> "Target node. Only allowed if the original VM is on shared storage.",
3028 description
=> "Override I/O bandwidth limit (in KiB/s).",
3032 default => 'clone limit from datacenter or storage config',
3042 my $rpcenv = PVE
::RPCEnvironment
::get
();
3043 my $authuser = $rpcenv->get_user();
3045 my $node = extract_param
($param, 'node');
3046 my $vmid = extract_param
($param, 'vmid');
3047 my $newid = extract_param
($param, 'newid');
3048 my $pool = extract_param
($param, 'pool');
3049 $rpcenv->check_pool_exist($pool) if defined($pool);
3051 my $snapname = extract_param
($param, 'snapname');
3052 my $storage = extract_param
($param, 'storage');
3053 my $format = extract_param
($param, 'format');
3054 my $target = extract_param
($param, 'target');
3056 my $localnode = PVE
::INotify
::nodename
();
3058 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3062 PVE
::Cluster
::check_node_exists
($target) if $target;
3064 my $storecfg = PVE
::Storage
::config
();
3067 # check if storage is enabled on local node
3068 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3070 # check if storage is available on target node
3071 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
3072 # clone only works if target storage is shared
3073 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3074 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3078 PVE
::Cluster
::check_cfs_quorum
();
3080 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3082 my $load_and_check = sub {
3083 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3084 PVE
::QemuConfig-
>check_lock($conf);
3086 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3087 die "unexpected state change\n" if $verify_running != $running;
3089 die "snapshot '$snapname' does not exist\n"
3090 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3092 my $full = $param->{full
} // !PVE
::QemuConfig-
>is_template($conf);
3094 die "parameter 'storage' not allowed for linked clones\n"
3095 if defined($storage) && !$full;
3097 die "parameter 'format' not allowed for linked clones\n"
3098 if defined($format) && !$full;
3100 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3102 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3104 die "can't clone VM to node '$target' (VM uses local storage)\n"
3105 if $target && !$sharedvm;
3107 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3108 die "unable to create VM $newid: config file already exists\n"
3111 my $newconf = { lock => 'clone' };
3116 foreach my $opt (keys %$oldconf) {
3117 my $value = $oldconf->{$opt};
3119 # do not copy snapshot related info
3120 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3121 $opt eq 'vmstate' || $opt eq 'snapstate';
3123 # no need to copy unused images, because VMID(owner) changes anyways
3124 next if $opt =~ m/^unused\d+$/;
3126 # always change MAC! address
3127 if ($opt =~ m/^net(\d+)$/) {
3128 my $net = PVE
::QemuServer
::parse_net
($value);
3129 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3130 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3131 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3132 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3133 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3134 die "unable to parse drive options for '$opt'\n" if !$drive;
3135 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3136 $newconf->{$opt} = $value; # simply copy configuration
3138 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3139 die "Full clone feature is not supported for drive '$opt'\n"
3140 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3141 $fullclone->{$opt} = 1;
3143 # not full means clone instead of copy
3144 die "Linked clone feature is not supported for drive '$opt'\n"
3145 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3147 $drives->{$opt} = $drive;
3148 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3149 push @$vollist, $drive->{file
};
3152 # copy everything else
3153 $newconf->{$opt} = $value;
3157 return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
3161 my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
3163 # auto generate a new uuid
3164 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3165 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3166 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3167 # auto generate a new vmgenid only if the option was set for template
3168 if ($newconf->{vmgenid
}) {
3169 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3172 delete $newconf->{template
};
3174 if ($param->{name
}) {
3175 $newconf->{name
} = $param->{name
};
3177 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3180 if ($param->{description
}) {
3181 $newconf->{description
} = $param->{description
};
3184 # create empty/temp config - this fails if VM already exists on other node
3185 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3186 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3188 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3190 my $newvollist = [];
3197 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3199 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3201 my $bwlimit = extract_param
($param, 'bwlimit');
3203 my $total_jobs = scalar(keys %{$drives});
3206 foreach my $opt (sort keys %$drives) {
3207 my $drive = $drives->{$opt};
3208 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3209 my $completion = $skipcomplete ?
'skip' : 'complete';
3211 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3212 my $storage_list = [ $src_sid ];
3213 push @$storage_list, $storage if defined($storage);
3214 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3216 my $newdrive = PVE
::QemuServer
::clone_disk
(
3235 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3237 PVE
::QemuConfig-
>write_config($newid, $newconf);
3241 delete $newconf->{lock};
3243 # do not write pending changes
3244 if (my @changes = keys %{$newconf->{pending
}}) {
3245 my $pending = join(',', @changes);
3246 warn "found pending changes for '$pending', discarding for clone\n";
3247 delete $newconf->{pending
};
3250 PVE
::QemuConfig-
>write_config($newid, $newconf);
3253 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3254 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3255 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3257 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3258 die "Failed to move config to node '$target' - rename failed: $!\n"
3259 if !rename($conffile, $newconffile);
3262 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3265 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3266 sleep 1; # some storage like rbd need to wait before release volume - really?
3268 foreach my $volid (@$newvollist) {
3269 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3273 PVE
::Firewall
::remove_vmfw_conf
($newid);
3275 unlink $conffile; # avoid races -> last thing before die
3277 die "clone failed: $err";
3283 # Aquire exclusive lock lock for $newid
3284 my $lock_target_vm = sub {
3285 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3288 my $lock_source_vm = sub {
3289 # exclusive lock if VM is running - else shared lock is enough;
3291 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3293 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3297 $load_and_check->(); # early checks before forking/locking
3299 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
3302 __PACKAGE__-
>register_method({
3303 name
=> 'move_vm_disk',
3304 path
=> '{vmid}/move_disk',
3308 description
=> "Move volume to different storage or to a different VM.",
3310 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
3311 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
3312 "a disk to another VM, you need the permissions on the target VM as well.",
3313 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3316 additionalProperties
=> 0,
3318 node
=> get_standard_option
('pve-node'),
3319 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3320 'target-vmid' => get_standard_option
('pve-vmid', {
3321 completion
=> \
&PVE
::QemuServer
::complete_vmid
,
3326 description
=> "The disk you want to move.",
3327 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3329 storage
=> get_standard_option
('pve-storage-id', {
3330 description
=> "Target storage.",
3331 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3336 description
=> "Target Format.",
3337 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3342 description
=> "Delete the original disk after successful copy. By default the"
3343 ." original disk is kept as unused disk.",
3349 description
=> 'Prevent changes if current configuration file has different SHA1"
3350 ." digest. This can be used to prevent concurrent modifications.',
3355 description
=> "Override I/O bandwidth limit (in KiB/s).",
3359 default => 'move limit from datacenter or storage config',
3363 description
=> "The config key the disk will be moved to on the target VM"
3364 ." (for example, ide0 or scsi1). Default is the source disk key.",
3365 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names_with_unused
()],
3368 'target-digest' => {
3370 description
=> 'Prevent changes if the current config file of the target VM has a"
3371 ." different SHA1 digest. This can be used to detect concurrent modifications.',
3379 description
=> "the task ID.",
3384 my $rpcenv = PVE
::RPCEnvironment
::get
();
3385 my $authuser = $rpcenv->get_user();
3387 my $node = extract_param
($param, 'node');
3388 my $vmid = extract_param
($param, 'vmid');
3389 my $target_vmid = extract_param
($param, 'target-vmid');
3390 my $digest = extract_param
($param, 'digest');
3391 my $target_digest = extract_param
($param, 'target-digest');
3392 my $disk = extract_param
($param, 'disk');
3393 my $target_disk = extract_param
($param, 'target-disk') // $disk;
3394 my $storeid = extract_param
($param, 'storage');
3395 my $format = extract_param
($param, 'format');
3397 my $storecfg = PVE
::Storage
::config
();
3399 my $move_updatefn = sub {
3400 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3401 PVE
::QemuConfig-
>check_lock($conf);
3403 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
3405 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3407 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3409 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3410 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3412 my $old_volid = $drive->{file
};
3414 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3415 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3419 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3420 (!$format || !$oldfmt || $oldfmt eq $format);
3422 # this only checks snapshots because $disk is passed!
3423 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
(
3429 die "you can't move a disk with snapshots and delete the source\n"
3430 if $snapshotted && $param->{delete};
3432 PVE
::Cluster
::log_msg
(
3435 "move disk VM $vmid: move --disk $disk --storage $storeid"
3438 my $running = PVE
::QemuServer
::check_running
($vmid);
3440 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3443 my $newvollist = [];
3449 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3451 warn "moving disk with snapshots, snapshots will not be moved!\n"
3454 my $bwlimit = extract_param
($param, 'bwlimit');
3455 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
3457 [$oldstoreid, $storeid],
3461 my $newdrive = PVE
::QemuServer
::clone_disk
(
3479 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3481 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3483 # convert moved disk to base if part of template
3484 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3485 if PVE
::QemuConfig-
>is_template($conf);
3487 PVE
::QemuConfig-
>write_config($vmid, $conf);
3489 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3490 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3491 eval { mon_cmd
($vmid, "guest-fstrim") };
3495 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3496 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3502 foreach my $volid (@$newvollist) {
3503 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3506 die "storage migration failed: $err";
3509 if ($param->{delete}) {
3511 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3512 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3518 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3521 my $load_and_check_reassign_configs = sub {
3522 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
3524 die "could not find VM ${vmid}\n" if !exists($vmlist->{$vmid});
3525 die "could not find target VM ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
3527 my $source_node = $vmlist->{$vmid}->{node
};
3528 my $target_node = $vmlist->{$target_vmid}->{node
};
3530 die "Both VMs need to be on the same node ($source_node != $target_node)\n"
3531 if $source_node ne $target_node;
3533 my $source_conf = PVE
::QemuConfig-
>load_config($vmid);
3534 PVE
::QemuConfig-
>check_lock($source_conf);
3535 my $target_conf = PVE
::QemuConfig-
>load_config($target_vmid);
3536 PVE
::QemuConfig-
>check_lock($target_conf);
3538 die "Can't move disks from or to template VMs\n"
3539 if ($source_conf->{template
} || $target_conf->{template
});
3542 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
3543 die "VM ${vmid}: $@" if $@;
3546 if ($target_digest) {
3547 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
3548 die "VM ${target_vmid}: $@" if $@;
3551 die "Disk '${disk}' for VM '$vmid' does not exist\n" if !defined($source_conf->{$disk});
3553 die "Target disk key '${target_disk}' is already in use for VM '$target_vmid'\n"
3554 if $target_conf->{$target_disk};
3556 my $drive = PVE
::QemuServer
::parse_drive
(
3558 $source_conf->{$disk},
3560 die "failed to parse source disk - $@\n" if !$drive;
3562 my $source_volid = $drive->{file
};
3564 die "disk '${disk}' has no associated volume\n" if !$source_volid;
3565 die "CD drive contents can't be moved to another VM\n"
3566 if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3568 my $storeid = PVE
::Storage
::parse_volume_id
($source_volid, 1);
3569 die "Volume '$source_volid' not managed by PVE\n" if !defined($storeid);
3571 die "Can't move disk used by a snapshot to another VM\n"
3572 if PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $source_conf, $disk, $source_volid);
3573 die "Storage does not support moving of this disk to another VM\n"
3574 if (!PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid));
3575 die "Cannot move disk to another VM while the source VM is running - detach first\n"
3576 if PVE
::QemuServer
::check_running
($vmid) && $disk !~ m/^unused\d+$/;
3578 # now re-parse using target disk slot format
3579 if ($target_disk =~ /^unused\d+$/) {
3580 $drive = PVE
::QemuServer
::parse_drive
(
3585 $drive = PVE
::QemuServer
::parse_drive
(
3587 $source_conf->{$disk},
3590 die "failed to parse source disk for target disk format - $@\n" if !$drive;
3592 my $repl_conf = PVE
::ReplicationConfig-
>new();
3593 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
3594 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3595 die "Cannot move disk to a replicated VM. Storage does not support replication!\n"
3596 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
3599 return ($source_conf, $target_conf, $drive);
3604 print STDERR
"$msg\n";
3607 my $disk_reassignfn = sub {
3608 return PVE
::QemuConfig-
>lock_config($vmid, sub {
3609 return PVE
::QemuConfig-
>lock_config($target_vmid, sub {
3610 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
3612 my $source_volid = $drive->{file
};
3614 print "moving disk '$disk' from VM '$vmid' to '$target_vmid'\n";
3615 my ($storeid, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
3617 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
3619 my $new_volid = PVE
::Storage
::rename_volume
(
3625 $drive->{file
} = $new_volid;
3627 delete $source_conf->{$disk};
3628 print "removing disk '${disk}' from VM '${vmid}' config\n";
3629 PVE
::QemuConfig-
>write_config($vmid, $source_conf);
3631 my $drive_string = PVE
::QemuServer
::print_drive
($drive);
3633 if ($target_disk =~ /^unused\d+$/) {
3634 $target_conf->{$target_disk} = $drive_string;
3635 PVE
::QemuConfig-
>write_config($target_vmid, $target_conf);
3640 vmid
=> $target_vmid,
3641 digest
=> $target_digest,
3642 $target_disk => $drive_string,
3648 # remove possible replication snapshots
3649 if (PVE
::Storage
::volume_has_feature
(
3655 PVE
::Replication
::prepare
(
3665 print "Failed to remove replication snapshots on moved disk " .
3666 "'$target_disk'. Manual cleanup could be necessary.\n";
3673 if ($target_vmid && $storeid) {
3674 my $msg = "either set 'storage' or 'target-vmid', but not both";
3675 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3676 } elsif ($target_vmid) {
3677 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
3678 if $authuser ne 'root@pam';
3680 raise_param_exc
({ 'target-vmid' => "must be different than source VMID to reassign disk" })
3681 if $vmid eq $target_vmid;
3683 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
3684 my $storage = PVE
::Storage
::parse_volume_id
($drive->{file
});
3685 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
3687 return $rpcenv->fork_worker(
3689 "${vmid}-${disk}>${target_vmid}-${target_disk}",
3693 } elsif ($storeid) {
3694 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3696 die "cannot move disk '$disk', only configured disks can be moved to another storage\n"
3697 if $disk =~ m/^unused\d+$/;
3698 return PVE
::QemuConfig-
>lock_config($vmid, $move_updatefn);
3700 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
3701 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
3705 my $check_vm_disks_local = sub {
3706 my ($storecfg, $vmconf, $vmid) = @_;
3708 my $local_disks = {};
3710 # add some more information to the disks e.g. cdrom
3711 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3712 my ($volid, $attr) = @_;
3714 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3716 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3717 return if $scfg->{shared
};
3719 # The shared attr here is just a special case where the vdisk
3720 # is marked as shared manually
3721 return if $attr->{shared
};
3722 return if $attr->{cdrom
} and $volid eq "none";
3724 if (exists $local_disks->{$volid}) {
3725 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3727 $local_disks->{$volid} = $attr;
3728 # ensure volid is present in case it's needed
3729 $local_disks->{$volid}->{volid
} = $volid;
3733 return $local_disks;
3736 __PACKAGE__-
>register_method({
3737 name
=> 'migrate_vm_precondition',
3738 path
=> '{vmid}/migrate',
3742 description
=> "Get preconditions for migration.",
3744 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3747 additionalProperties
=> 0,
3749 node
=> get_standard_option
('pve-node'),
3750 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3751 target
=> get_standard_option
('pve-node', {
3752 description
=> "Target node.",
3753 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3761 running
=> { type
=> 'boolean' },
3765 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3767 not_allowed_nodes
=> {
3770 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3774 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3776 local_resources
=> {
3778 description
=> "List local resources e.g. pci, usb"
3785 my $rpcenv = PVE
::RPCEnvironment
::get
();
3787 my $authuser = $rpcenv->get_user();
3789 PVE
::Cluster
::check_cfs_quorum
();
3793 my $vmid = extract_param
($param, 'vmid');
3794 my $target = extract_param
($param, 'target');
3795 my $localnode = PVE
::INotify
::nodename
();
3799 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3800 my $storecfg = PVE
::Storage
::config
();
3803 # try to detect errors early
3804 PVE
::QemuConfig-
>check_lock($vmconf);
3806 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3808 # if vm is not running, return target nodes where local storage is available
3809 # for offline migration
3810 if (!$res->{running
}) {
3811 $res->{allowed_nodes
} = [];
3812 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3813 delete $checked_nodes->{$localnode};
3815 foreach my $node (keys %$checked_nodes) {
3816 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3817 push @{$res->{allowed_nodes
}}, $node;
3821 $res->{not_allowed_nodes
} = $checked_nodes;
3825 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3826 $res->{local_disks
} = [ values %$local_disks ];;
3828 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3830 $res->{local_resources
} = $local_resources;
3837 __PACKAGE__-
>register_method({
3838 name
=> 'migrate_vm',
3839 path
=> '{vmid}/migrate',
3843 description
=> "Migrate virtual machine. Creates a new migration task.",
3845 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3848 additionalProperties
=> 0,
3850 node
=> get_standard_option
('pve-node'),
3851 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3852 target
=> get_standard_option
('pve-node', {
3853 description
=> "Target node.",
3854 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3858 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3863 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3868 enum
=> ['secure', 'insecure'],
3869 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3872 migration_network
=> {
3873 type
=> 'string', format
=> 'CIDR',
3874 description
=> "CIDR of the (sub) network that is used for migration.",
3877 "with-local-disks" => {
3879 description
=> "Enable live storage migration for local disk",
3882 targetstorage
=> get_standard_option
('pve-targetstorage', {
3883 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3886 description
=> "Override I/O bandwidth limit (in KiB/s).",
3890 default => 'migrate limit from datacenter or storage config',
3896 description
=> "the task ID.",
3901 my $rpcenv = PVE
::RPCEnvironment
::get
();
3902 my $authuser = $rpcenv->get_user();
3904 my $target = extract_param
($param, 'target');
3906 my $localnode = PVE
::INotify
::nodename
();
3907 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3909 PVE
::Cluster
::check_cfs_quorum
();
3911 PVE
::Cluster
::check_node_exists
($target);
3913 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3915 my $vmid = extract_param
($param, 'vmid');
3917 raise_param_exc
({ force
=> "Only root may use this option." })
3918 if $param->{force
} && $authuser ne 'root@pam';
3920 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3921 if $param->{migration_type
} && $authuser ne 'root@pam';
3923 # allow root only until better network permissions are available
3924 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3925 if $param->{migration_network
} && $authuser ne 'root@pam';
3928 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3930 # try to detect errors early
3932 PVE
::QemuConfig-
>check_lock($conf);
3934 if (PVE
::QemuServer
::check_running
($vmid)) {
3935 die "can't migrate running VM without --online\n" if !$param->{online
};
3937 my $repl_conf = PVE
::ReplicationConfig-
>new();
3938 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3939 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3940 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3941 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3942 "target. Use 'force' to override.\n";
3945 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3946 $param->{online
} = 0;
3949 my $storecfg = PVE
::Storage
::config
();
3950 if (my $targetstorage = $param->{targetstorage
}) {
3951 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3952 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3955 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3956 if !defined($storagemap->{identity
});
3958 foreach my $target_sid (values %{$storagemap->{entries
}}) {
3959 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
3962 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
3963 if $storagemap->{default};
3965 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3966 if $storagemap->{identity
};
3968 $param->{storagemap
} = $storagemap;
3970 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3973 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3978 print "Requesting HA migration for VM $vmid to node $target\n";
3980 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3981 PVE
::Tools
::run_command
($cmd);
3985 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3990 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3994 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3997 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
4002 __PACKAGE__-
>register_method({
4004 path
=> '{vmid}/monitor',
4008 description
=> "Execute Qemu monitor commands.",
4010 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
4011 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
4014 additionalProperties
=> 0,
4016 node
=> get_standard_option
('pve-node'),
4017 vmid
=> get_standard_option
('pve-vmid'),
4020 description
=> "The monitor command.",
4024 returns
=> { type
=> 'string'},
4028 my $rpcenv = PVE
::RPCEnvironment
::get
();
4029 my $authuser = $rpcenv->get_user();
4032 my $command = shift;
4033 return $command =~ m/^\s*info(\s+|$)/
4034 || $command =~ m/^\s*help\s*$/;
4037 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
4038 if !&$is_ro($param->{command
});
4040 my $vmid = $param->{vmid
};
4042 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
4046 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
4048 $res = "ERROR: $@" if $@;
4053 __PACKAGE__-
>register_method({
4054 name
=> 'resize_vm',
4055 path
=> '{vmid}/resize',
4059 description
=> "Extend volume size.",
4061 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
4064 additionalProperties
=> 0,
4066 node
=> get_standard_option
('pve-node'),
4067 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4068 skiplock
=> get_standard_option
('skiplock'),
4071 description
=> "The disk you want to resize.",
4072 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4076 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
4077 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.",
4081 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
4087 returns
=> { type
=> 'null'},
4091 my $rpcenv = PVE
::RPCEnvironment
::get
();
4093 my $authuser = $rpcenv->get_user();
4095 my $node = extract_param
($param, 'node');
4097 my $vmid = extract_param
($param, 'vmid');
4099 my $digest = extract_param
($param, 'digest');
4101 my $disk = extract_param
($param, 'disk');
4103 my $sizestr = extract_param
($param, 'size');
4105 my $skiplock = extract_param
($param, 'skiplock');
4106 raise_param_exc
({ skiplock
=> "Only root may use this option." })
4107 if $skiplock && $authuser ne 'root@pam';
4109 my $storecfg = PVE
::Storage
::config
();
4111 my $updatefn = sub {
4113 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4115 die "checksum missmatch (file change by other user?)\n"
4116 if $digest && $digest ne $conf->{digest
};
4117 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
4119 die "disk '$disk' does not exist\n" if !$conf->{$disk};
4121 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
4123 my (undef, undef, undef, undef, undef, undef, $format) =
4124 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
4126 die "can't resize volume: $disk if snapshot exists\n"
4127 if %{$conf->{snapshots
}} && $format eq 'qcow2';
4129 my $volid = $drive->{file
};
4131 die "disk '$disk' has no associated volume\n" if !$volid;
4133 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
4135 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
4137 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
4139 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
4140 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
4142 die "Could not determine current size of volume '$volid'\n" if !defined($size);
4144 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
4145 my ($ext, $newsize, $unit) = ($1, $2, $4);
4148 $newsize = $newsize * 1024;
4149 } elsif ($unit eq 'M') {
4150 $newsize = $newsize * 1024 * 1024;
4151 } elsif ($unit eq 'G') {
4152 $newsize = $newsize * 1024 * 1024 * 1024;
4153 } elsif ($unit eq 'T') {
4154 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
4157 $newsize += $size if $ext;
4158 $newsize = int($newsize);
4160 die "shrinking disks is not supported\n" if $newsize < $size;
4162 return if $size == $newsize;
4164 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
4166 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
4168 $drive->{size
} = $newsize;
4169 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
4171 PVE
::QemuConfig-
>write_config($vmid, $conf);
4174 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4178 __PACKAGE__-
>register_method({
4179 name
=> 'snapshot_list',
4180 path
=> '{vmid}/snapshot',
4182 description
=> "List all snapshots.",
4184 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4187 protected
=> 1, # qemu pid files are only readable by root
4189 additionalProperties
=> 0,
4191 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4192 node
=> get_standard_option
('pve-node'),
4201 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
4205 description
=> "Snapshot includes RAM.",
4210 description
=> "Snapshot description.",
4214 description
=> "Snapshot creation time",
4216 renderer
=> 'timestamp',
4220 description
=> "Parent snapshot identifier.",
4226 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
4231 my $vmid = $param->{vmid
};
4233 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4234 my $snaphash = $conf->{snapshots
} || {};
4238 foreach my $name (keys %$snaphash) {
4239 my $d = $snaphash->{$name};
4242 snaptime
=> $d->{snaptime
} || 0,
4243 vmstate
=> $d->{vmstate
} ?
1 : 0,
4244 description
=> $d->{description
} || '',
4246 $item->{parent
} = $d->{parent
} if $d->{parent
};
4247 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
4251 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
4254 digest
=> $conf->{digest
},
4255 running
=> $running,
4256 description
=> "You are here!",
4258 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
4260 push @$res, $current;
4265 __PACKAGE__-
>register_method({
4267 path
=> '{vmid}/snapshot',
4271 description
=> "Snapshot a VM.",
4273 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4276 additionalProperties
=> 0,
4278 node
=> get_standard_option
('pve-node'),
4279 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4280 snapname
=> get_standard_option
('pve-snapshot-name'),
4284 description
=> "Save the vmstate",
4289 description
=> "A textual description or comment.",
4295 description
=> "the task ID.",
4300 my $rpcenv = PVE
::RPCEnvironment
::get
();
4302 my $authuser = $rpcenv->get_user();
4304 my $node = extract_param
($param, 'node');
4306 my $vmid = extract_param
($param, 'vmid');
4308 my $snapname = extract_param
($param, 'snapname');
4310 die "unable to use snapshot name 'current' (reserved name)\n"
4311 if $snapname eq 'current';
4313 die "unable to use snapshot name 'pending' (reserved name)\n"
4314 if lc($snapname) eq 'pending';
4317 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4318 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4319 $param->{description
});
4322 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4325 __PACKAGE__-
>register_method({
4326 name
=> 'snapshot_cmd_idx',
4327 path
=> '{vmid}/snapshot/{snapname}',
4334 additionalProperties
=> 0,
4336 vmid
=> get_standard_option
('pve-vmid'),
4337 node
=> get_standard_option
('pve-node'),
4338 snapname
=> get_standard_option
('pve-snapshot-name'),
4347 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4354 push @$res, { cmd
=> 'rollback' };
4355 push @$res, { cmd
=> 'config' };
4360 __PACKAGE__-
>register_method({
4361 name
=> 'update_snapshot_config',
4362 path
=> '{vmid}/snapshot/{snapname}/config',
4366 description
=> "Update snapshot metadata.",
4368 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4371 additionalProperties
=> 0,
4373 node
=> get_standard_option
('pve-node'),
4374 vmid
=> get_standard_option
('pve-vmid'),
4375 snapname
=> get_standard_option
('pve-snapshot-name'),
4379 description
=> "A textual description or comment.",
4383 returns
=> { type
=> 'null' },
4387 my $rpcenv = PVE
::RPCEnvironment
::get
();
4389 my $authuser = $rpcenv->get_user();
4391 my $vmid = extract_param
($param, 'vmid');
4393 my $snapname = extract_param
($param, 'snapname');
4395 return if !defined($param->{description
});
4397 my $updatefn = sub {
4399 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4401 PVE
::QemuConfig-
>check_lock($conf);
4403 my $snap = $conf->{snapshots
}->{$snapname};
4405 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4407 $snap->{description
} = $param->{description
} if defined($param->{description
});
4409 PVE
::QemuConfig-
>write_config($vmid, $conf);
4412 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4417 __PACKAGE__-
>register_method({
4418 name
=> 'get_snapshot_config',
4419 path
=> '{vmid}/snapshot/{snapname}/config',
4422 description
=> "Get snapshot configuration",
4424 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4427 additionalProperties
=> 0,
4429 node
=> get_standard_option
('pve-node'),
4430 vmid
=> get_standard_option
('pve-vmid'),
4431 snapname
=> get_standard_option
('pve-snapshot-name'),
4434 returns
=> { type
=> "object" },
4438 my $rpcenv = PVE
::RPCEnvironment
::get
();
4440 my $authuser = $rpcenv->get_user();
4442 my $vmid = extract_param
($param, 'vmid');
4444 my $snapname = extract_param
($param, 'snapname');
4446 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4448 my $snap = $conf->{snapshots
}->{$snapname};
4450 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4455 __PACKAGE__-
>register_method({
4457 path
=> '{vmid}/snapshot/{snapname}/rollback',
4461 description
=> "Rollback VM state to specified snapshot.",
4463 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4466 additionalProperties
=> 0,
4468 node
=> get_standard_option
('pve-node'),
4469 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4470 snapname
=> get_standard_option
('pve-snapshot-name'),
4475 description
=> "the task ID.",
4480 my $rpcenv = PVE
::RPCEnvironment
::get
();
4482 my $authuser = $rpcenv->get_user();
4484 my $node = extract_param
($param, 'node');
4486 my $vmid = extract_param
($param, 'vmid');
4488 my $snapname = extract_param
($param, 'snapname');
4491 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4492 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4496 # hold migration lock, this makes sure that nobody create replication snapshots
4497 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4500 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4503 __PACKAGE__-
>register_method({
4504 name
=> 'delsnapshot',
4505 path
=> '{vmid}/snapshot/{snapname}',
4509 description
=> "Delete a VM snapshot.",
4511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4514 additionalProperties
=> 0,
4516 node
=> get_standard_option
('pve-node'),
4517 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4518 snapname
=> get_standard_option
('pve-snapshot-name'),
4522 description
=> "For removal from config file, even if removing disk snapshots fails.",
4528 description
=> "the task ID.",
4533 my $rpcenv = PVE
::RPCEnvironment
::get
();
4535 my $authuser = $rpcenv->get_user();
4537 my $node = extract_param
($param, 'node');
4539 my $vmid = extract_param
($param, 'vmid');
4541 my $snapname = extract_param
($param, 'snapname');
4544 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4545 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4548 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4551 __PACKAGE__-
>register_method({
4553 path
=> '{vmid}/template',
4557 description
=> "Create a Template.",
4559 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4560 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4563 additionalProperties
=> 0,
4565 node
=> get_standard_option
('pve-node'),
4566 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4570 description
=> "If you want to convert only 1 disk to base image.",
4571 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4578 description
=> "the task ID.",
4583 my $rpcenv = PVE
::RPCEnvironment
::get
();
4585 my $authuser = $rpcenv->get_user();
4587 my $node = extract_param
($param, 'node');
4589 my $vmid = extract_param
($param, 'vmid');
4591 my $disk = extract_param
($param, 'disk');
4593 my $load_and_check = sub {
4594 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4596 PVE
::QemuConfig-
>check_lock($conf);
4598 die "unable to create template, because VM contains snapshots\n"
4599 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4601 die "you can't convert a template to a template\n"
4602 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4604 die "you can't convert a VM to template if VM is running\n"
4605 if PVE
::QemuServer
::check_running
($vmid);
4610 $load_and_check->();
4613 PVE
::QemuConfig-
>lock_config($vmid, sub {
4614 my $conf = $load_and_check->();
4616 $conf->{template
} = 1;
4617 PVE
::QemuConfig-
>write_config($vmid, $conf);
4619 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4623 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4626 __PACKAGE__-
>register_method({
4627 name
=> 'cloudinit_generated_config_dump',
4628 path
=> '{vmid}/cloudinit/dump',
4631 description
=> "Get automatically generated cloudinit config.",
4633 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4636 additionalProperties
=> 0,
4638 node
=> get_standard_option
('pve-node'),
4639 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4641 description
=> 'Config type.',
4643 enum
=> ['user', 'network', 'meta'],
4653 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4655 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});