1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::RPCEnvironment
;
29 use PVE
::AccessControl
;
33 use PVE
::API2
::Firewall
::VM
;
34 use PVE
::API2
::Qemu
::Agent
;
35 use PVE
::VZDump
::Plugin
;
36 use PVE
::DataCenterConfig
;
40 if (!$ENV{PVE_GENERATING_DOCS
}) {
41 require PVE
::HA
::Env
::PVE2
;
42 import PVE
::HA
::Env
::PVE2
;
43 require PVE
::HA
::Config
;
44 import PVE
::HA
::Config
;
48 use Data
::Dumper
; # fixme: remove
50 use base
qw(PVE::RESTHandler);
52 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
54 my $resolve_cdrom_alias = sub {
57 if (my $value = $param->{cdrom
}) {
58 $value .= ",media=cdrom" if $value !~ m/media=/;
59 $param->{ide2
} = $value;
60 delete $param->{cdrom
};
64 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
65 my $check_storage_access = sub {
66 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
68 PVE
::QemuConfig-
>foreach_volume($settings, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
74 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
76 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
78 } elsif ($isCDROM && ($volid eq 'cdrom')) {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
81 my ($storeid, $size) = ($2 || $default_storage, $3);
82 die "no storage ID specified (and no default storage)\n" if !$storeid;
83 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
85 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
86 if !$scfg->{content
}->{images
};
88 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
92 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
93 if defined($settings->{vmstatestorage
});
96 my $check_storage_access_clone = sub {
97 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
101 PVE
::QemuConfig-
>foreach_volume($conf, sub {
102 my ($ds, $drive) = @_;
104 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
106 my $volid = $drive->{file
};
108 return if !$volid || $volid eq 'none';
111 if ($volid eq 'cdrom') {
112 $rpcenv->check($authuser, "/", ['Sys.Console']);
114 # we simply allow access
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
121 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
122 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
123 $sharedvm = 0 if !$scfg->{shared
};
125 $sid = $storage if $storage;
126 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
130 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
131 if defined($conf->{vmstatestorage
});
136 # Note: $pool is only needed when creating a VM, because pool permissions
137 # are automatically inherited if VM already exists inside a pool.
138 my $create_disks = sub {
139 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
146 my ($ds, $disk) = @_;
148 my $volid = $disk->{file
};
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
151 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
152 delete $disk->{size
};
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
154 } elsif (defined($volname) && $volname eq 'cloudinit') {
155 $storeid = $storeid // $default_storage;
156 die "no storage ID specified (and no default storage)\n" if !$storeid;
157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
158 my $name = "vm-$vmid-cloudinit";
162 $fmt = $disk->{format
} // "qcow2";
165 $fmt = $disk->{format
} // "raw";
168 # Initial disk created with 4 MB and aligned to 4MB on regeneration
169 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
170 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
171 $disk->{file
} = $volid;
172 $disk->{media
} = 'cdrom';
173 push @$vollist, $volid;
174 delete $disk->{format
}; # no longer needed
175 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
176 } elsif ($volid =~ $NEW_DISK_RE) {
177 my ($storeid, $size) = ($2 || $default_storage, $3);
178 die "no storage ID specified (and no default storage)\n" if !$storeid;
179 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
180 my $fmt = $disk->{format
} || $defformat;
182 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
185 if ($ds eq 'efidisk0') {
186 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
188 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
190 push @$vollist, $volid;
191 $disk->{file
} = $volid;
192 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
193 delete $disk->{format
}; # no longer needed
194 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
197 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
199 my $volid_is_new = 1;
202 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
203 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
208 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
210 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
212 die "volume $volid does not exist\n" if !$size;
214 $disk->{size
} = $size;
217 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
221 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
223 # free allocated images on error
225 syslog
('err', "VM $vmid creating disks failed");
226 foreach my $volid (@$vollist) {
227 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
233 # modify vm config if everything went well
234 foreach my $ds (keys %$res) {
235 $conf->{$ds} = $res->{$ds};
241 my $check_cpu_model_access = sub {
242 my ($rpcenv, $authuser, $new, $existing) = @_;
244 return if !defined($new->{cpu
});
246 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
247 return if !$cpu || !$cpu->{cputype
}; # always allow default
248 my $cputype = $cpu->{cputype
};
250 if ($existing && $existing->{cpu
}) {
251 # changing only other settings doesn't require permissions for CPU model
252 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
253 return if $existingCpu->{cputype
} eq $cputype;
256 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
257 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
272 my $memoryoptions = {
278 my $hwtypeoptions = {
291 my $generaloptions = {
298 'migrate_downtime' => 1,
299 'migrate_speed' => 1,
312 my $vmpoweroptions = {
319 'vmstatestorage' => 1,
322 my $cloudinitoptions = {
332 my $check_vm_create_serial_perm = sub {
333 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
335 return 1 if $authuser eq 'root@pam';
337 foreach my $opt (keys %{$param}) {
338 next if $opt !~ m/^serial\d+$/;
340 if ($param->{$opt} eq 'socket') {
341 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
343 die "only root can set '$opt' config for real devices\n";
350 my $check_vm_create_usb_perm = sub {
351 my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
353 return 1 if $authuser eq 'root@pam';
355 foreach my $opt (keys %{$param}) {
356 next if $opt !~ m/^usb\d+$/;
358 if ($param->{$opt} =~ m/spice/) {
359 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
361 die "only root can set '$opt' config for real devices\n";
368 my $check_vm_modify_config_perm = sub {
369 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
371 return 1 if $authuser eq 'root@pam';
373 foreach my $opt (@$key_list) {
374 # some checks (e.g., disk, serial port, usb) need to be done somewhere
375 # else, as there the permission can be value dependend
376 next if PVE
::QemuServer
::is_valid_drivename
($opt);
377 next if $opt eq 'cdrom';
378 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
381 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
382 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
383 } elsif ($memoryoptions->{$opt}) {
384 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
385 } elsif ($hwtypeoptions->{$opt}) {
386 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
387 } elsif ($generaloptions->{$opt}) {
388 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
389 # special case for startup since it changes host behaviour
390 if ($opt eq 'startup') {
391 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
393 } elsif ($vmpoweroptions->{$opt}) {
394 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
395 } elsif ($diskoptions->{$opt}) {
396 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
397 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
398 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
399 } elsif ($cloudinitoptions->{$opt}) {
400 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
401 } elsif ($opt eq 'vmstate') {
402 # the user needs Disk and PowerMgmt privileges to change the vmstate
403 # also needs privileges on the storage, that will be checked later
404 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
406 # catches hostpci\d+, args, lock, etc.
407 # new options will be checked here
408 die "only root can set '$opt' config\n";
415 __PACKAGE__-
>register_method({
419 description
=> "Virtual machine index (per node).",
421 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
425 protected
=> 1, # qemu pid files are only readable by root
427 additionalProperties
=> 0,
429 node
=> get_standard_option
('pve-node'),
433 description
=> "Determine the full status of active VMs.",
441 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
443 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
448 my $rpcenv = PVE
::RPCEnvironment
::get
();
449 my $authuser = $rpcenv->get_user();
451 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
454 foreach my $vmid (keys %$vmstatus) {
455 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
457 my $data = $vmstatus->{$vmid};
464 my $parse_restore_archive = sub {
465 my ($storecfg, $archive) = @_;
467 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
469 if (defined($archive_storeid)) {
470 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
471 if ($scfg->{type
} eq 'pbs') {
478 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
486 __PACKAGE__-
>register_method({
490 description
=> "Create or restore a virtual machine.",
492 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
493 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
494 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
495 user
=> 'all', # check inside
500 additionalProperties
=> 0,
501 properties
=> PVE
::QemuServer
::json_config_properties
(
503 node
=> get_standard_option
('pve-node'),
504 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
506 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
510 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
512 storage
=> get_standard_option
('pve-storage-id', {
513 description
=> "Default storage.",
515 completion
=> \
&PVE
::QemuServer
::complete_storage
,
520 description
=> "Allow to overwrite existing VM.",
521 requires
=> 'archive',
526 description
=> "Assign a unique random ethernet address.",
527 requires
=> 'archive',
531 type
=> 'string', format
=> 'pve-poolid',
532 description
=> "Add the VM to the specified pool.",
535 description
=> "Override I/O bandwidth limit (in KiB/s).",
539 default => 'restore limit from datacenter or storage config',
545 description
=> "Start VM after it was created successfully.",
555 my $rpcenv = PVE
::RPCEnvironment
::get
();
556 my $authuser = $rpcenv->get_user();
558 my $node = extract_param
($param, 'node');
559 my $vmid = extract_param
($param, 'vmid');
561 my $archive = extract_param
($param, 'archive');
562 my $is_restore = !!$archive;
564 my $bwlimit = extract_param
($param, 'bwlimit');
565 my $force = extract_param
($param, 'force');
566 my $pool = extract_param
($param, 'pool');
567 my $start_after_create = extract_param
($param, 'start');
568 my $storage = extract_param
($param, 'storage');
569 my $unique = extract_param
($param, 'unique');
571 if (defined(my $ssh_keys = $param->{sshkeys
})) {
572 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
573 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
576 PVE
::Cluster
::check_cfs_quorum
();
578 my $filename = PVE
::QemuConfig-
>config_file($vmid);
579 my $storecfg = PVE
::Storage
::config
();
581 if (defined($pool)) {
582 $rpcenv->check_pool_exist($pool);
585 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
586 if defined($storage);
588 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
590 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
592 } elsif ($archive && $force && (-f
$filename) &&
593 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
594 # OK: user has VM.Backup permissions, and want to restore an existing VM
600 &$resolve_cdrom_alias($param);
602 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
604 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
606 &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
607 &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
609 &$check_cpu_model_access($rpcenv, $authuser, $param);
611 foreach my $opt (keys %$param) {
612 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
613 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
614 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
616 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
617 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
621 PVE
::QemuServer
::add_random_macs
($param);
623 my $keystr = join(' ', keys %$param);
624 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
626 if ($archive eq '-') {
627 die "pipe requires cli environment\n"
628 if $rpcenv->{type
} ne 'cli';
629 $archive = { type
=> 'pipe' };
631 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
633 $archive = $parse_restore_archive->($storecfg, $archive);
637 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
639 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
640 die "$emsg $@" if $@;
642 my $restorefn = sub {
643 my $conf = PVE
::QemuConfig-
>load_config($vmid);
645 PVE
::QemuConfig-
>check_protection($conf, $emsg);
647 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
650 my $restore_options = {
656 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
657 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
658 } elsif ($archive->{type
} eq 'pbs') {
659 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
661 die "unknown backup archive type\n";
663 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
664 # Convert restored VM to template if backup was VM template
665 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
666 warn "Convert to template.\n";
667 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
671 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
674 # ensure no old replication state are exists
675 PVE
::ReplicationState
::delete_guest_states
($vmid);
677 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
679 if ($start_after_create) {
680 print "Execute autostart\n";
681 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
687 # ensure no old replication state are exists
688 PVE
::ReplicationState
::delete_guest_states
($vmid);
692 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
696 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
698 if (!$conf->{boot
}) {
699 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
700 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
703 # auto generate uuid if user did not specify smbios1 option
704 if (!$conf->{smbios1
}) {
705 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
708 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
709 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
712 my $machine = $conf->{machine
};
713 if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
714 # always pin Windows' machine version on create, they get to easily confused
715 if (PVE
::QemuServer
::windows_version
($conf->{ostype
})) {
716 $conf->{machine
} = PVE
::QemuServer
::windows_get_pinned_machine_version
($machine);
720 PVE
::QemuConfig-
>write_config($vmid, $conf);
726 foreach my $volid (@$vollist) {
727 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
733 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
736 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
738 if ($start_after_create) {
739 print "Execute autostart\n";
740 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
745 my ($code, $worker_name);
747 $worker_name = 'qmrestore';
749 eval { $restorefn->() };
751 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
757 $worker_name = 'qmcreate';
759 eval { $createfn->() };
762 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
763 unlink($conffile) or die "failed to remove config file: $!\n";
771 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
774 __PACKAGE__-
>register_method({
779 description
=> "Directory index",
784 additionalProperties
=> 0,
786 node
=> get_standard_option
('pve-node'),
787 vmid
=> get_standard_option
('pve-vmid'),
795 subdir
=> { type
=> 'string' },
798 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
804 { subdir
=> 'config' },
805 { subdir
=> 'pending' },
806 { subdir
=> 'status' },
807 { subdir
=> 'unlink' },
808 { subdir
=> 'vncproxy' },
809 { subdir
=> 'termproxy' },
810 { subdir
=> 'migrate' },
811 { subdir
=> 'resize' },
812 { subdir
=> 'move' },
814 { subdir
=> 'rrddata' },
815 { subdir
=> 'monitor' },
816 { subdir
=> 'agent' },
817 { subdir
=> 'snapshot' },
818 { subdir
=> 'spiceproxy' },
819 { subdir
=> 'sendkey' },
820 { subdir
=> 'firewall' },
826 __PACKAGE__-
>register_method ({
827 subclass
=> "PVE::API2::Firewall::VM",
828 path
=> '{vmid}/firewall',
831 __PACKAGE__-
>register_method ({
832 subclass
=> "PVE::API2::Qemu::Agent",
833 path
=> '{vmid}/agent',
836 __PACKAGE__-
>register_method({
838 path
=> '{vmid}/rrd',
840 protected
=> 1, # fixme: can we avoid that?
842 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
844 description
=> "Read VM RRD statistics (returns PNG)",
846 additionalProperties
=> 0,
848 node
=> get_standard_option
('pve-node'),
849 vmid
=> get_standard_option
('pve-vmid'),
851 description
=> "Specify the time frame you are interested in.",
853 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
856 description
=> "The list of datasources you want to display.",
857 type
=> 'string', format
=> 'pve-configid-list',
860 description
=> "The RRD consolidation function",
862 enum
=> [ 'AVERAGE', 'MAX' ],
870 filename
=> { type
=> 'string' },
876 return PVE
::RRD
::create_rrd_graph
(
877 "pve2-vm/$param->{vmid}", $param->{timeframe
},
878 $param->{ds
}, $param->{cf
});
882 __PACKAGE__-
>register_method({
884 path
=> '{vmid}/rrddata',
886 protected
=> 1, # fixme: can we avoid that?
888 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
890 description
=> "Read VM RRD statistics",
892 additionalProperties
=> 0,
894 node
=> get_standard_option
('pve-node'),
895 vmid
=> get_standard_option
('pve-vmid'),
897 description
=> "Specify the time frame you are interested in.",
899 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
902 description
=> "The RRD consolidation function",
904 enum
=> [ 'AVERAGE', 'MAX' ],
919 return PVE
::RRD
::create_rrd_data
(
920 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
924 __PACKAGE__-
>register_method({
926 path
=> '{vmid}/config',
929 description
=> "Get the virtual machine configuration with pending configuration " .
930 "changes applied. Set the 'current' parameter to get the current configuration instead.",
932 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
935 additionalProperties
=> 0,
937 node
=> get_standard_option
('pve-node'),
938 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
940 description
=> "Get current values (instead of pending values).",
945 snapshot
=> get_standard_option
('pve-snapshot-name', {
946 description
=> "Fetch config values from given snapshot.",
949 my ($cmd, $pname, $cur, $args) = @_;
950 PVE
::QemuConfig-
>snapshot_list($args->[0]);
956 description
=> "The VM configuration.",
958 properties
=> PVE
::QemuServer
::json_config_properties
({
961 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
968 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
969 current
=> "cannot use 'snapshot' parameter with 'current'"})
970 if ($param->{snapshot
} && $param->{current
});
973 if ($param->{snapshot
}) {
974 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
976 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
978 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
983 __PACKAGE__-
>register_method({
984 name
=> 'vm_pending',
985 path
=> '{vmid}/pending',
988 description
=> "Get the virtual machine configuration with both current and pending values.",
990 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
993 additionalProperties
=> 0,
995 node
=> get_standard_option
('pve-node'),
996 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1005 description
=> "Configuration option name.",
1009 description
=> "Current value.",
1014 description
=> "Pending value.",
1019 description
=> "Indicates a pending delete request if present and not 0. " .
1020 "The value 2 indicates a force-delete request.",
1032 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1034 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
1036 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
1037 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
1039 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
1042 # POST/PUT {vmid}/config implementation
1044 # The original API used PUT (idempotent) an we assumed that all operations
1045 # are fast. But it turned out that almost any configuration change can
1046 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1047 # time to complete and have side effects (not idempotent).
1049 # The new implementation uses POST and forks a worker process. We added
1050 # a new option 'background_delay'. If specified we wait up to
1051 # 'background_delay' second for the worker task to complete. It returns null
1052 # if the task is finished within that time, else we return the UPID.
1054 my $update_vm_api = sub {
1055 my ($param, $sync) = @_;
1057 my $rpcenv = PVE
::RPCEnvironment
::get
();
1059 my $authuser = $rpcenv->get_user();
1061 my $node = extract_param
($param, 'node');
1063 my $vmid = extract_param
($param, 'vmid');
1065 my $digest = extract_param
($param, 'digest');
1067 my $background_delay = extract_param
($param, 'background_delay');
1069 if (defined(my $cipassword = $param->{cipassword
})) {
1070 # Same logic as in cloud-init (but with the regex fixed...)
1071 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1072 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1075 my @paramarr = (); # used for log message
1076 foreach my $key (sort keys %$param) {
1077 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1078 push @paramarr, "-$key", $value;
1081 my $skiplock = extract_param
($param, 'skiplock');
1082 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1083 if $skiplock && $authuser ne 'root@pam';
1085 my $delete_str = extract_param
($param, 'delete');
1087 my $revert_str = extract_param
($param, 'revert');
1089 my $force = extract_param
($param, 'force');
1091 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1092 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1093 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1096 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1098 my $storecfg = PVE
::Storage
::config
();
1100 my $defaults = PVE
::QemuServer
::load_defaults
();
1102 &$resolve_cdrom_alias($param);
1104 # now try to verify all parameters
1107 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1108 if (!PVE
::QemuServer
::option_exists
($opt)) {
1109 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1112 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1113 "-revert $opt' at the same time" })
1114 if defined($param->{$opt});
1116 $revert->{$opt} = 1;
1120 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1121 $opt = 'ide2' if $opt eq 'cdrom';
1123 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1124 "-delete $opt' at the same time" })
1125 if defined($param->{$opt});
1127 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1128 "-revert $opt' at the same time" })
1131 if (!PVE
::QemuServer
::option_exists
($opt)) {
1132 raise_param_exc
({ delete => "unknown option '$opt'" });
1138 my $repl_conf = PVE
::ReplicationConfig-
>new();
1139 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1140 my $check_replication = sub {
1142 return if !$is_replicated;
1143 my $volid = $drive->{file
};
1144 return if !$volid || !($drive->{replicate
}//1);
1145 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1148 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1149 if !defined($storeid);
1151 return if defined($volname) && $volname eq 'cloudinit';
1154 if ($volid =~ $NEW_DISK_RE) {
1156 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1158 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1160 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1161 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1162 return if $scfg->{shared
};
1163 die "cannot add non-replicatable volume to a replicated VM\n";
1166 foreach my $opt (keys %$param) {
1167 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1168 # cleanup drive path
1169 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1170 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1171 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1172 $check_replication->($drive);
1173 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1174 } elsif ($opt =~ m/^net(\d+)$/) {
1176 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1177 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1178 } elsif ($opt eq 'vmgenid') {
1179 if ($param->{$opt} eq '1') {
1180 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1182 } elsif ($opt eq 'hookscript') {
1183 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1184 raise_param_exc
({ $opt => $@ }) if $@;
1188 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1190 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1192 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1194 my $updatefn = sub {
1196 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1198 die "checksum missmatch (file change by other user?)\n"
1199 if $digest && $digest ne $conf->{digest
};
1201 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1203 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1204 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1205 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1206 delete $conf->{lock}; # for check lock check, not written out
1207 push @delete, 'lock'; # this is the real deal to write it out
1209 push @delete, 'runningmachine' if $conf->{runningmachine
};
1210 push @delete, 'runningcpu' if $conf->{runningcpu
};
1213 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1215 foreach my $opt (keys %$revert) {
1216 if (defined($conf->{$opt})) {
1217 $param->{$opt} = $conf->{$opt};
1218 } elsif (defined($conf->{pending
}->{$opt})) {
1223 if ($param->{memory
} || defined($param->{balloon
})) {
1224 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1225 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1227 die "balloon value too large (must be smaller than assigned memory)\n"
1228 if $balloon && $balloon > $maxmem;
1231 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1235 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1237 # write updates to pending section
1239 my $modified = {}; # record what $option we modify
1242 if (my $boot = $conf->{boot
}) {
1243 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1244 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1246 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1248 foreach my $opt (@delete) {
1249 $modified->{$opt} = 1;
1250 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1252 # value of what we want to delete, independent if pending or not
1253 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1254 if (!defined($val)) {
1255 warn "cannot delete '$opt' - not set in current configuration!\n";
1256 $modified->{$opt} = 0;
1259 my $is_pending_val = defined($conf->{pending
}->{$opt});
1260 delete $conf->{pending
}->{$opt};
1262 # remove from bootorder if necessary
1263 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1264 @bootorder = grep {$_ ne $opt} @bootorder;
1265 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1266 $modified->{boot
} = 1;
1269 if ($opt =~ m/^unused/) {
1270 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1271 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1272 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1273 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1274 delete $conf->{$opt};
1275 PVE
::QemuConfig-
>write_config($vmid, $conf);
1277 } elsif ($opt eq 'vmstate') {
1278 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1279 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1280 delete $conf->{$opt};
1281 PVE
::QemuConfig-
>write_config($vmid, $conf);
1283 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1284 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1285 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1286 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1287 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1289 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1291 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1293 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1294 PVE
::QemuConfig-
>write_config($vmid, $conf);
1295 } elsif ($opt =~ m/^serial\d+$/) {
1296 if ($val eq 'socket') {
1297 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1298 } elsif ($authuser ne 'root@pam') {
1299 die "only root can delete '$opt' config for real devices\n";
1301 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1302 PVE
::QemuConfig-
>write_config($vmid, $conf);
1303 } elsif ($opt =~ m/^usb\d+$/) {
1304 if ($val =~ m/spice/) {
1305 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1306 } elsif ($authuser ne 'root@pam') {
1307 die "only root can delete '$opt' config for real devices\n";
1309 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1310 PVE
::QemuConfig-
>write_config($vmid, $conf);
1312 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1313 PVE
::QemuConfig-
>write_config($vmid, $conf);
1317 foreach my $opt (keys %$param) { # add/change
1318 $modified->{$opt} = 1;
1319 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1320 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1322 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1324 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1325 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1326 # FIXME: cloudinit: CDROM or Disk?
1327 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1328 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1330 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1332 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1333 if defined($conf->{pending
}->{$opt});
1335 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1336 } elsif ($opt =~ m/^serial\d+/) {
1337 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1338 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1339 } elsif ($authuser ne 'root@pam') {
1340 die "only root can modify '$opt' config for real devices\n";
1342 $conf->{pending
}->{$opt} = $param->{$opt};
1343 } elsif ($opt =~ m/^usb\d+/) {
1344 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1345 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1346 } elsif ($authuser ne 'root@pam') {
1347 die "only root can modify '$opt' config for real devices\n";
1349 $conf->{pending
}->{$opt} = $param->{$opt};
1351 $conf->{pending
}->{$opt} = $param->{$opt};
1353 if ($opt eq 'boot') {
1354 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1355 if ($new_bootcfg->{order
}) {
1356 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1357 for my $dev (@devs) {
1358 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1359 my $deleted = grep {$_ eq $dev} @delete;
1360 die "invalid bootorder: device '$dev' does not exist'\n"
1361 if !$exists || $deleted;
1364 # remove legacy boot order settings if new one set
1365 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1366 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1367 if $conf->{bootdisk
};
1371 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1372 PVE
::QemuConfig-
>write_config($vmid, $conf);
1375 # remove pending changes when nothing changed
1376 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1377 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1378 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1380 return if !scalar(keys %{$conf->{pending
}});
1382 my $running = PVE
::QemuServer
::check_running
($vmid);
1384 # apply pending changes
1386 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1390 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1392 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1394 raise_param_exc
($errors) if scalar(keys %$errors);
1403 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1405 if ($background_delay) {
1407 # Note: It would be better to do that in the Event based HTTPServer
1408 # to avoid blocking call to sleep.
1410 my $end_time = time() + $background_delay;
1412 my $task = PVE
::Tools
::upid_decode
($upid);
1415 while (time() < $end_time) {
1416 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1418 sleep(1); # this gets interrupted when child process ends
1422 my $status = PVE
::Tools
::upid_read_status
($upid);
1423 return if $status eq 'OK';
1432 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1435 my $vm_config_perm_list = [
1440 'VM.Config.Network',
1442 'VM.Config.Options',
1443 'VM.Config.Cloudinit',
1446 __PACKAGE__-
>register_method({
1447 name
=> 'update_vm_async',
1448 path
=> '{vmid}/config',
1452 description
=> "Set virtual machine options (asynchrounous API).",
1454 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1457 additionalProperties
=> 0,
1458 properties
=> PVE
::QemuServer
::json_config_properties
(
1460 node
=> get_standard_option
('pve-node'),
1461 vmid
=> get_standard_option
('pve-vmid'),
1462 skiplock
=> get_standard_option
('skiplock'),
1464 type
=> 'string', format
=> 'pve-configid-list',
1465 description
=> "A list of settings you want to delete.",
1469 type
=> 'string', format
=> 'pve-configid-list',
1470 description
=> "Revert a pending change.",
1475 description
=> $opt_force_description,
1477 requires
=> 'delete',
1481 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1485 background_delay
=> {
1487 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1498 code
=> $update_vm_api,
1501 __PACKAGE__-
>register_method({
1502 name
=> 'update_vm',
1503 path
=> '{vmid}/config',
1507 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1509 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1512 additionalProperties
=> 0,
1513 properties
=> PVE
::QemuServer
::json_config_properties
(
1515 node
=> get_standard_option
('pve-node'),
1516 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1517 skiplock
=> get_standard_option
('skiplock'),
1519 type
=> 'string', format
=> 'pve-configid-list',
1520 description
=> "A list of settings you want to delete.",
1524 type
=> 'string', format
=> 'pve-configid-list',
1525 description
=> "Revert a pending change.",
1530 description
=> $opt_force_description,
1532 requires
=> 'delete',
1536 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1542 returns
=> { type
=> 'null' },
1545 &$update_vm_api($param, 1);
1550 __PACKAGE__-
>register_method({
1551 name
=> 'destroy_vm',
1556 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1557 ." and firewall rules",
1559 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1562 additionalProperties
=> 0,
1564 node
=> get_standard_option
('pve-node'),
1565 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1566 skiplock
=> get_standard_option
('skiplock'),
1569 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1572 'destroy-unreferenced-disks' => {
1574 description
=> "If set, destroy additionally all disks not referenced in the config"
1575 ." but with a matching VMID from all enabled storages.",
1577 default => 1, # FIXME: replace to false in PVE 7.0, this is dangerous!
1587 my $rpcenv = PVE
::RPCEnvironment
::get
();
1588 my $authuser = $rpcenv->get_user();
1589 my $vmid = $param->{vmid
};
1591 my $skiplock = $param->{skiplock
};
1592 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1593 if $skiplock && $authuser ne 'root@pam';
1595 my $early_checks = sub {
1597 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1598 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1600 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1602 if (!$param->{purge
}) {
1603 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1605 # don't allow destroy if with replication jobs but no purge param
1606 my $repl_conf = PVE
::ReplicationConfig-
>new();
1607 $repl_conf->check_for_existing_jobs($vmid);
1610 die "VM $vmid is running - destroy failed\n"
1611 if PVE
::QemuServer
::check_running
($vmid);
1621 my $storecfg = PVE
::Storage
::config
();
1623 syslog
('info', "destroy VM $vmid: $upid\n");
1624 PVE
::QemuConfig-
>lock_config($vmid, sub {
1625 # repeat, config might have changed
1626 my $ha_managed = $early_checks->();
1628 # FIXME: drop fallback to true with 7.0, to dangerous for default
1629 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'} // 1;
1631 PVE
::QemuServer
::destroy_vm
(
1634 $skiplock, { lock => 'destroyed' },
1635 $purge_unreferenced,
1638 PVE
::AccessControl
::remove_vm_access
($vmid);
1639 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1640 if ($param->{purge
}) {
1641 print "purging VM $vmid from related configurations..\n";
1642 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1643 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1646 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1647 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1651 # only now remove the zombie config, else we can have reuse race
1652 PVE
::QemuConfig-
>destroy_config($vmid);
1656 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1659 __PACKAGE__-
>register_method({
1661 path
=> '{vmid}/unlink',
1665 description
=> "Unlink/delete disk images.",
1667 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1670 additionalProperties
=> 0,
1672 node
=> get_standard_option
('pve-node'),
1673 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1675 type
=> 'string', format
=> 'pve-configid-list',
1676 description
=> "A list of disk IDs you want to delete.",
1680 description
=> $opt_force_description,
1685 returns
=> { type
=> 'null'},
1689 $param->{delete} = extract_param
($param, 'idlist');
1691 __PACKAGE__-
>update_vm($param);
1696 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1697 my $gen_rand_chars = sub {
1700 die "invalid length $length" if $length < 1;
1702 my $min = ord('!'); # first printable ascii
1704 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1705 die "failed to generate random bytes!\n"
1708 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1715 __PACKAGE__-
>register_method({
1717 path
=> '{vmid}/vncproxy',
1721 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1723 description
=> "Creates a TCP VNC proxy connections.",
1725 additionalProperties
=> 0,
1727 node
=> get_standard_option
('pve-node'),
1728 vmid
=> get_standard_option
('pve-vmid'),
1732 description
=> "starts websockify instead of vncproxy",
1734 'generate-password' => {
1738 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1743 additionalProperties
=> 0,
1745 user
=> { type
=> 'string' },
1746 ticket
=> { type
=> 'string' },
1749 description
=> "Returned if requested with 'generate-password' param."
1750 ." Consists of printable ASCII characters ('!' .. '~').",
1753 cert
=> { type
=> 'string' },
1754 port
=> { type
=> 'integer' },
1755 upid
=> { type
=> 'string' },
1761 my $rpcenv = PVE
::RPCEnvironment
::get
();
1763 my $authuser = $rpcenv->get_user();
1765 my $vmid = $param->{vmid
};
1766 my $node = $param->{node
};
1767 my $websocket = $param->{websocket
};
1769 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1773 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1774 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1777 my $authpath = "/vms/$vmid";
1779 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1780 my $password = $ticket;
1781 if ($param->{'generate-password'}) {
1782 $password = $gen_rand_chars->(8);
1785 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1791 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1792 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1793 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1794 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1795 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1797 $family = PVE
::Tools
::get_host_address_family
($node);
1800 my $port = PVE
::Tools
::next_vnc_port
($family);
1807 syslog
('info', "starting vnc proxy $upid\n");
1811 if (defined($serial)) {
1813 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1815 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1816 '-timeout', $timeout, '-authpath', $authpath,
1817 '-perm', 'Sys.Console'];
1819 if ($param->{websocket
}) {
1820 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1821 push @$cmd, '-notls', '-listen', 'localhost';
1824 push @$cmd, '-c', @$remcmd, @$termcmd;
1826 PVE
::Tools
::run_command
($cmd);
1830 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1832 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1834 my $sock = IO
::Socket
::IP-
>new(
1839 GetAddrInfoFlags
=> 0,
1840 ) or die "failed to create socket: $!\n";
1841 # Inside the worker we shouldn't have any previous alarms
1842 # running anyway...:
1844 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1846 accept(my $cli, $sock) or die "connection failed: $!\n";
1849 if (PVE
::Tools
::run_command
($cmd,
1850 output
=> '>&'.fileno($cli),
1851 input
=> '<&'.fileno($cli),
1854 die "Failed to run vncproxy.\n";
1861 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1863 PVE
::Tools
::wait_for_vnc_port
($port);
1872 $res->{password
} = $password if $param->{'generate-password'};
1877 __PACKAGE__-
>register_method({
1878 name
=> 'termproxy',
1879 path
=> '{vmid}/termproxy',
1883 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1885 description
=> "Creates a TCP proxy connections.",
1887 additionalProperties
=> 0,
1889 node
=> get_standard_option
('pve-node'),
1890 vmid
=> get_standard_option
('pve-vmid'),
1894 enum
=> [qw(serial0 serial1 serial2 serial3)],
1895 description
=> "opens a serial terminal (defaults to display)",
1900 additionalProperties
=> 0,
1902 user
=> { type
=> 'string' },
1903 ticket
=> { type
=> 'string' },
1904 port
=> { type
=> 'integer' },
1905 upid
=> { type
=> 'string' },
1911 my $rpcenv = PVE
::RPCEnvironment
::get
();
1913 my $authuser = $rpcenv->get_user();
1915 my $vmid = $param->{vmid
};
1916 my $node = $param->{node
};
1917 my $serial = $param->{serial
};
1919 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1921 if (!defined($serial)) {
1923 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1924 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1928 my $authpath = "/vms/$vmid";
1930 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1935 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1936 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1937 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1938 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1939 push @$remcmd, '--';
1941 $family = PVE
::Tools
::get_host_address_family
($node);
1944 my $port = PVE
::Tools
::next_vnc_port
($family);
1946 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1947 push @$termcmd, '-iface', $serial if $serial;
1952 syslog
('info', "starting qemu termproxy $upid\n");
1954 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1955 '--perm', 'VM.Console', '--'];
1956 push @$cmd, @$remcmd, @$termcmd;
1958 PVE
::Tools
::run_command
($cmd);
1961 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1963 PVE
::Tools
::wait_for_vnc_port
($port);
1973 __PACKAGE__-
>register_method({
1974 name
=> 'vncwebsocket',
1975 path
=> '{vmid}/vncwebsocket',
1978 description
=> "You also need to pass a valid ticket (vncticket).",
1979 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1981 description
=> "Opens a weksocket for VNC traffic.",
1983 additionalProperties
=> 0,
1985 node
=> get_standard_option
('pve-node'),
1986 vmid
=> get_standard_option
('pve-vmid'),
1988 description
=> "Ticket from previous call to vncproxy.",
1993 description
=> "Port number returned by previous vncproxy call.",
2003 port
=> { type
=> 'string' },
2009 my $rpcenv = PVE
::RPCEnvironment
::get
();
2011 my $authuser = $rpcenv->get_user();
2013 my $vmid = $param->{vmid
};
2014 my $node = $param->{node
};
2016 my $authpath = "/vms/$vmid";
2018 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
2020 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
2022 # Note: VNC ports are acessible from outside, so we do not gain any
2023 # security if we verify that $param->{port} belongs to VM $vmid. This
2024 # check is done by verifying the VNC ticket (inside VNC protocol).
2026 my $port = $param->{port
};
2028 return { port
=> $port };
2031 __PACKAGE__-
>register_method({
2032 name
=> 'spiceproxy',
2033 path
=> '{vmid}/spiceproxy',
2038 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2040 description
=> "Returns a SPICE configuration to connect to the VM.",
2042 additionalProperties
=> 0,
2044 node
=> get_standard_option
('pve-node'),
2045 vmid
=> get_standard_option
('pve-vmid'),
2046 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
2049 returns
=> get_standard_option
('remote-viewer-config'),
2053 my $rpcenv = PVE
::RPCEnvironment
::get
();
2055 my $authuser = $rpcenv->get_user();
2057 my $vmid = $param->{vmid
};
2058 my $node = $param->{node
};
2059 my $proxy = $param->{proxy
};
2061 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2062 my $title = "VM $vmid";
2063 $title .= " - ". $conf->{name
} if $conf->{name
};
2065 my $port = PVE
::QemuServer
::spice_port
($vmid);
2067 my ($ticket, undef, $remote_viewer_config) =
2068 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2070 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2071 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2073 return $remote_viewer_config;
2076 __PACKAGE__-
>register_method({
2078 path
=> '{vmid}/status',
2081 description
=> "Directory index",
2086 additionalProperties
=> 0,
2088 node
=> get_standard_option
('pve-node'),
2089 vmid
=> get_standard_option
('pve-vmid'),
2097 subdir
=> { type
=> 'string' },
2100 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2106 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2109 { subdir
=> 'current' },
2110 { subdir
=> 'start' },
2111 { subdir
=> 'stop' },
2112 { subdir
=> 'reset' },
2113 { subdir
=> 'shutdown' },
2114 { subdir
=> 'suspend' },
2115 { subdir
=> 'reboot' },
2121 __PACKAGE__-
>register_method({
2122 name
=> 'vm_status',
2123 path
=> '{vmid}/status/current',
2126 protected
=> 1, # qemu pid files are only readable by root
2127 description
=> "Get virtual machine status.",
2129 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2132 additionalProperties
=> 0,
2134 node
=> get_standard_option
('pve-node'),
2135 vmid
=> get_standard_option
('pve-vmid'),
2141 %$PVE::QemuServer
::vmstatus_return_properties
,
2143 description
=> "HA manager service status.",
2147 description
=> "Qemu VGA configuration supports spice.",
2152 description
=> "Qemu GuestAgent enabled in config.",
2162 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2164 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2165 my $status = $vmstatus->{$param->{vmid
}};
2167 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2169 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2170 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2175 __PACKAGE__-
>register_method({
2177 path
=> '{vmid}/status/start',
2181 description
=> "Start virtual machine.",
2183 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2186 additionalProperties
=> 0,
2188 node
=> get_standard_option
('pve-node'),
2189 vmid
=> get_standard_option
('pve-vmid',
2190 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2191 skiplock
=> get_standard_option
('skiplock'),
2192 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2193 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2196 enum
=> ['secure', 'insecure'],
2197 description
=> "Migration traffic is encrypted using an SSH " .
2198 "tunnel by default. On secure, completely private networks " .
2199 "this can be disabled to increase performance.",
2202 migration_network
=> {
2203 type
=> 'string', format
=> 'CIDR',
2204 description
=> "CIDR of the (sub) network that is used for migration.",
2207 machine
=> get_standard_option
('pve-qemu-machine'),
2209 description
=> "Override QEMU's -cpu argument with the given string.",
2213 targetstorage
=> get_standard_option
('pve-targetstorage'),
2215 description
=> "Wait maximal timeout seconds.",
2218 default => 'max(30, vm memory in GiB)',
2229 my $rpcenv = PVE
::RPCEnvironment
::get
();
2230 my $authuser = $rpcenv->get_user();
2232 my $node = extract_param
($param, 'node');
2233 my $vmid = extract_param
($param, 'vmid');
2234 my $timeout = extract_param
($param, 'timeout');
2236 my $machine = extract_param
($param, 'machine');
2237 my $force_cpu = extract_param
($param, 'force-cpu');
2239 my $get_root_param = sub {
2240 my $value = extract_param
($param, $_[0]);
2241 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2242 if $value && $authuser ne 'root@pam';
2246 my $stateuri = $get_root_param->('stateuri');
2247 my $skiplock = $get_root_param->('skiplock');
2248 my $migratedfrom = $get_root_param->('migratedfrom');
2249 my $migration_type = $get_root_param->('migration_type');
2250 my $migration_network = $get_root_param->('migration_network');
2251 my $targetstorage = $get_root_param->('targetstorage');
2255 if ($targetstorage) {
2256 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2258 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2259 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2263 # read spice ticket from STDIN
2265 my $nbd_protocol_version = 0;
2266 my $replicated_volumes = {};
2267 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2268 while (defined(my $line = <STDIN
>)) {
2270 if ($line =~ m/^spice_ticket: (.+)$/) {
2272 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2273 $nbd_protocol_version = $1;
2274 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2275 $replicated_volumes->{$1} = 1;
2277 # fallback for old source node
2278 $spice_ticket = $line;
2283 PVE
::Cluster
::check_cfs_quorum
();
2285 my $storecfg = PVE
::Storage
::config
();
2287 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2291 print "Requesting HA start for VM $vmid\n";
2293 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2294 PVE
::Tools
::run_command
($cmd);
2298 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2305 syslog
('info', "start VM $vmid: $upid\n");
2307 my $migrate_opts = {
2308 migratedfrom
=> $migratedfrom,
2309 spice_ticket
=> $spice_ticket,
2310 network
=> $migration_network,
2311 type
=> $migration_type,
2312 storagemap
=> $storagemap,
2313 nbd_proto_version
=> $nbd_protocol_version,
2314 replicated_volumes
=> $replicated_volumes,
2318 statefile
=> $stateuri,
2319 skiplock
=> $skiplock,
2320 forcemachine
=> $machine,
2321 timeout
=> $timeout,
2322 forcecpu
=> $force_cpu,
2325 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2329 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2333 __PACKAGE__-
>register_method({
2335 path
=> '{vmid}/status/stop',
2339 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2340 "is akin to pulling the power plug of a running computer and may damage the VM data",
2342 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2345 additionalProperties
=> 0,
2347 node
=> get_standard_option
('pve-node'),
2348 vmid
=> get_standard_option
('pve-vmid',
2349 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2350 skiplock
=> get_standard_option
('skiplock'),
2351 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2353 description
=> "Wait maximal timeout seconds.",
2359 description
=> "Do not deactivate storage volumes.",
2372 my $rpcenv = PVE
::RPCEnvironment
::get
();
2373 my $authuser = $rpcenv->get_user();
2375 my $node = extract_param
($param, 'node');
2376 my $vmid = extract_param
($param, 'vmid');
2378 my $skiplock = extract_param
($param, 'skiplock');
2379 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2380 if $skiplock && $authuser ne 'root@pam';
2382 my $keepActive = extract_param
($param, 'keepActive');
2383 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2384 if $keepActive && $authuser ne 'root@pam';
2386 my $migratedfrom = extract_param
($param, 'migratedfrom');
2387 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2388 if $migratedfrom && $authuser ne 'root@pam';
2391 my $storecfg = PVE
::Storage
::config
();
2393 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2398 print "Requesting HA stop for VM $vmid\n";
2400 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2401 PVE
::Tools
::run_command
($cmd);
2405 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2411 syslog
('info', "stop VM $vmid: $upid\n");
2413 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2414 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2418 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2422 __PACKAGE__-
>register_method({
2424 path
=> '{vmid}/status/reset',
2428 description
=> "Reset virtual machine.",
2430 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2433 additionalProperties
=> 0,
2435 node
=> get_standard_option
('pve-node'),
2436 vmid
=> get_standard_option
('pve-vmid',
2437 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2438 skiplock
=> get_standard_option
('skiplock'),
2447 my $rpcenv = PVE
::RPCEnvironment
::get
();
2449 my $authuser = $rpcenv->get_user();
2451 my $node = extract_param
($param, 'node');
2453 my $vmid = extract_param
($param, 'vmid');
2455 my $skiplock = extract_param
($param, 'skiplock');
2456 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2457 if $skiplock && $authuser ne 'root@pam';
2459 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2464 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2469 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2472 __PACKAGE__-
>register_method({
2473 name
=> 'vm_shutdown',
2474 path
=> '{vmid}/status/shutdown',
2478 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2479 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2481 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2484 additionalProperties
=> 0,
2486 node
=> get_standard_option
('pve-node'),
2487 vmid
=> get_standard_option
('pve-vmid',
2488 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2489 skiplock
=> get_standard_option
('skiplock'),
2491 description
=> "Wait maximal timeout seconds.",
2497 description
=> "Make sure the VM stops.",
2503 description
=> "Do not deactivate storage volumes.",
2516 my $rpcenv = PVE
::RPCEnvironment
::get
();
2517 my $authuser = $rpcenv->get_user();
2519 my $node = extract_param
($param, 'node');
2520 my $vmid = extract_param
($param, 'vmid');
2522 my $skiplock = extract_param
($param, 'skiplock');
2523 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2524 if $skiplock && $authuser ne 'root@pam';
2526 my $keepActive = extract_param
($param, 'keepActive');
2527 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2528 if $keepActive && $authuser ne 'root@pam';
2530 my $storecfg = PVE
::Storage
::config
();
2534 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2535 # otherwise, we will infer a shutdown command, but run into the timeout,
2536 # then when the vm is resumed, it will instantly shutdown
2538 # checking the qmp status here to get feedback to the gui/cli/api
2539 # and the status query should not take too long
2540 if (PVE
::QemuServer
::vm_is_paused
($vmid)) {
2541 if ($param->{forceStop
}) {
2542 warn "VM is paused - stop instead of shutdown\n";
2545 die "VM is paused - cannot shutdown\n";
2549 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2551 my $timeout = $param->{timeout
} // 60;
2555 print "Requesting HA stop for VM $vmid\n";
2557 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2558 PVE
::Tools
::run_command
($cmd);
2562 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2569 syslog
('info', "shutdown VM $vmid: $upid\n");
2571 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2572 $shutdown, $param->{forceStop
}, $keepActive);
2576 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2580 __PACKAGE__-
>register_method({
2581 name
=> 'vm_reboot',
2582 path
=> '{vmid}/status/reboot',
2586 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2588 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2591 additionalProperties
=> 0,
2593 node
=> get_standard_option
('pve-node'),
2594 vmid
=> get_standard_option
('pve-vmid',
2595 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2597 description
=> "Wait maximal timeout seconds for the shutdown.",
2610 my $rpcenv = PVE
::RPCEnvironment
::get
();
2611 my $authuser = $rpcenv->get_user();
2613 my $node = extract_param
($param, 'node');
2614 my $vmid = extract_param
($param, 'vmid');
2616 die "VM is paused - cannot shutdown\n" if PVE
::QemuServer
::vm_is_paused
($vmid);
2618 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2623 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2624 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2628 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2631 __PACKAGE__-
>register_method({
2632 name
=> 'vm_suspend',
2633 path
=> '{vmid}/status/suspend',
2637 description
=> "Suspend virtual machine.",
2639 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2640 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2641 " on the storage for the vmstate.",
2642 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2645 additionalProperties
=> 0,
2647 node
=> get_standard_option
('pve-node'),
2648 vmid
=> get_standard_option
('pve-vmid',
2649 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2650 skiplock
=> get_standard_option
('skiplock'),
2655 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2657 statestorage
=> get_standard_option
('pve-storage-id', {
2658 description
=> "The storage for the VM state",
2659 requires
=> 'todisk',
2661 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
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 my $todisk = extract_param
($param, 'todisk') // 0;
2679 my $statestorage = extract_param
($param, 'statestorage');
2681 my $skiplock = extract_param
($param, 'skiplock');
2682 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2683 if $skiplock && $authuser ne 'root@pam';
2685 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2687 die "Cannot suspend HA managed VM to disk\n"
2688 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2690 # early check for storage permission, for better user feedback
2692 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2694 if (!$statestorage) {
2695 # get statestorage from config if none is given
2696 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2697 my $storecfg = PVE
::Storage
::config
();
2698 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2701 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2707 syslog
('info', "suspend VM $vmid: $upid\n");
2709 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2714 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2715 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2718 __PACKAGE__-
>register_method({
2719 name
=> 'vm_resume',
2720 path
=> '{vmid}/status/resume',
2724 description
=> "Resume virtual machine.",
2726 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2729 additionalProperties
=> 0,
2731 node
=> get_standard_option
('pve-node'),
2732 vmid
=> get_standard_option
('pve-vmid',
2733 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2734 skiplock
=> get_standard_option
('skiplock'),
2735 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2745 my $rpcenv = PVE
::RPCEnvironment
::get
();
2747 my $authuser = $rpcenv->get_user();
2749 my $node = extract_param
($param, 'node');
2751 my $vmid = extract_param
($param, 'vmid');
2753 my $skiplock = extract_param
($param, 'skiplock');
2754 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2755 if $skiplock && $authuser ne 'root@pam';
2757 my $nocheck = extract_param
($param, 'nocheck');
2758 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2759 if $nocheck && $authuser ne 'root@pam';
2761 my $to_disk_suspended;
2763 PVE
::QemuConfig-
>lock_config($vmid, sub {
2764 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2765 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2769 die "VM $vmid not running\n"
2770 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2775 syslog
('info', "resume VM $vmid: $upid\n");
2777 if (!$to_disk_suspended) {
2778 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2780 my $storecfg = PVE
::Storage
::config
();
2781 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2787 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2790 __PACKAGE__-
>register_method({
2791 name
=> 'vm_sendkey',
2792 path
=> '{vmid}/sendkey',
2796 description
=> "Send key event to virtual machine.",
2798 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2801 additionalProperties
=> 0,
2803 node
=> get_standard_option
('pve-node'),
2804 vmid
=> get_standard_option
('pve-vmid',
2805 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2806 skiplock
=> get_standard_option
('skiplock'),
2808 description
=> "The key (qemu monitor encoding).",
2813 returns
=> { type
=> 'null'},
2817 my $rpcenv = PVE
::RPCEnvironment
::get
();
2819 my $authuser = $rpcenv->get_user();
2821 my $node = extract_param
($param, 'node');
2823 my $vmid = extract_param
($param, 'vmid');
2825 my $skiplock = extract_param
($param, 'skiplock');
2826 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2827 if $skiplock && $authuser ne 'root@pam';
2829 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2834 __PACKAGE__-
>register_method({
2835 name
=> 'vm_feature',
2836 path
=> '{vmid}/feature',
2840 description
=> "Check if feature for virtual machine is available.",
2842 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2845 additionalProperties
=> 0,
2847 node
=> get_standard_option
('pve-node'),
2848 vmid
=> get_standard_option
('pve-vmid'),
2850 description
=> "Feature to check.",
2852 enum
=> [ 'snapshot', 'clone', 'copy' ],
2854 snapname
=> get_standard_option
('pve-snapshot-name', {
2862 hasFeature
=> { type
=> 'boolean' },
2865 items
=> { type
=> 'string' },
2872 my $node = extract_param
($param, 'node');
2874 my $vmid = extract_param
($param, 'vmid');
2876 my $snapname = extract_param
($param, 'snapname');
2878 my $feature = extract_param
($param, 'feature');
2880 my $running = PVE
::QemuServer
::check_running
($vmid);
2882 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2885 my $snap = $conf->{snapshots
}->{$snapname};
2886 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2889 my $storecfg = PVE
::Storage
::config
();
2891 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2892 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2895 hasFeature
=> $hasFeature,
2896 nodes
=> [ keys %$nodelist ],
2900 __PACKAGE__-
>register_method({
2902 path
=> '{vmid}/clone',
2906 description
=> "Create a copy of virtual machine/template.",
2908 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2909 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2910 "'Datastore.AllocateSpace' on any used storage.",
2913 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2915 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2916 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2921 additionalProperties
=> 0,
2923 node
=> get_standard_option
('pve-node'),
2924 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2925 newid
=> get_standard_option
('pve-vmid', {
2926 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2927 description
=> 'VMID for the clone.' }),
2930 type
=> 'string', format
=> 'dns-name',
2931 description
=> "Set a name for the new VM.",
2936 description
=> "Description for the new VM.",
2940 type
=> 'string', format
=> 'pve-poolid',
2941 description
=> "Add the new VM to the specified pool.",
2943 snapname
=> get_standard_option
('pve-snapshot-name', {
2946 storage
=> get_standard_option
('pve-storage-id', {
2947 description
=> "Target storage for full clone.",
2951 description
=> "Target format for file storage. Only valid for full clone.",
2954 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2959 description
=> "Create a full copy of all disks. This is always done when " .
2960 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2962 target
=> get_standard_option
('pve-node', {
2963 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2967 description
=> "Override I/O bandwidth limit (in KiB/s).",
2971 default => 'clone limit from datacenter or storage config',
2981 my $rpcenv = PVE
::RPCEnvironment
::get
();
2982 my $authuser = $rpcenv->get_user();
2984 my $node = extract_param
($param, 'node');
2985 my $vmid = extract_param
($param, 'vmid');
2986 my $newid = extract_param
($param, 'newid');
2987 my $pool = extract_param
($param, 'pool');
2988 $rpcenv->check_pool_exist($pool) if defined($pool);
2990 my $snapname = extract_param
($param, 'snapname');
2991 my $storage = extract_param
($param, 'storage');
2992 my $format = extract_param
($param, 'format');
2993 my $target = extract_param
($param, 'target');
2995 my $localnode = PVE
::INotify
::nodename
();
2997 if ($target && ($target eq $localnode || $target eq 'localhost')) {
3001 PVE
::Cluster
::check_node_exists
($target) if $target;
3003 my $storecfg = PVE
::Storage
::config
();
3006 # check if storage is enabled on local node
3007 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
3009 # check if storage is available on target node
3010 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
3011 # clone only works if target storage is shared
3012 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
3013 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
3017 PVE
::Cluster
::check_cfs_quorum
();
3019 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
3022 # do all tests after lock but before forking worker - if possible
3024 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3025 PVE
::QemuConfig-
>check_lock($conf);
3027 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
3028 die "unexpected state change\n" if $verify_running != $running;
3030 die "snapshot '$snapname' does not exist\n"
3031 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
3033 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
3035 die "parameter 'storage' not allowed for linked clones\n"
3036 if defined($storage) && !$full;
3038 die "parameter 'format' not allowed for linked clones\n"
3039 if defined($format) && !$full;
3041 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3043 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3045 die "can't clone VM to node '$target' (VM uses local storage)\n"
3046 if $target && !$sharedvm;
3048 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3049 die "unable to create VM $newid: config file already exists\n"
3052 my $newconf = { lock => 'clone' };
3057 foreach my $opt (keys %$oldconf) {
3058 my $value = $oldconf->{$opt};
3060 # do not copy snapshot related info
3061 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3062 $opt eq 'vmstate' || $opt eq 'snapstate';
3064 # no need to copy unused images, because VMID(owner) changes anyways
3065 next if $opt =~ m/^unused\d+$/;
3067 # always change MAC! address
3068 if ($opt =~ m/^net(\d+)$/) {
3069 my $net = PVE
::QemuServer
::parse_net
($value);
3070 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3071 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3072 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3073 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3074 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3075 die "unable to parse drive options for '$opt'\n" if !$drive;
3076 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3077 $newconf->{$opt} = $value; # simply copy configuration
3079 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3080 die "Full clone feature is not supported for drive '$opt'\n"
3081 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3082 $fullclone->{$opt} = 1;
3084 # not full means clone instead of copy
3085 die "Linked clone feature is not supported for drive '$opt'\n"
3086 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3088 $drives->{$opt} = $drive;
3089 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3090 push @$vollist, $drive->{file
};
3093 # copy everything else
3094 $newconf->{$opt} = $value;
3098 # auto generate a new uuid
3099 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3100 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3101 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3102 # auto generate a new vmgenid only if the option was set for template
3103 if ($newconf->{vmgenid
}) {
3104 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3107 delete $newconf->{template
};
3109 if ($param->{name
}) {
3110 $newconf->{name
} = $param->{name
};
3112 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3115 if ($param->{description
}) {
3116 $newconf->{description
} = $param->{description
};
3119 # create empty/temp config - this fails if VM already exists on other node
3120 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3121 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3126 my $newvollist = [];
3133 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3135 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3137 my $bwlimit = extract_param
($param, 'bwlimit');
3139 my $total_jobs = scalar(keys %{$drives});
3142 foreach my $opt (keys %$drives) {
3143 my $drive = $drives->{$opt};
3144 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3145 my $completion = $skipcomplete ?
'skip' : 'complete';
3147 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3148 my $storage_list = [ $src_sid ];
3149 push @$storage_list, $storage if defined($storage);
3150 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3152 my $newdrive = PVE
::QemuServer
::clone_disk
(
3171 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3173 PVE
::QemuConfig-
>write_config($newid, $newconf);
3177 delete $newconf->{lock};
3179 # do not write pending changes
3180 if (my @changes = keys %{$newconf->{pending
}}) {
3181 my $pending = join(',', @changes);
3182 warn "found pending changes for '$pending', discarding for clone\n";
3183 delete $newconf->{pending
};
3186 PVE
::QemuConfig-
>write_config($newid, $newconf);
3189 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3190 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3191 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3193 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3194 die "Failed to move config to node '$target' - rename failed: $!\n"
3195 if !rename($conffile, $newconffile);
3198 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3201 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3202 sleep 1; # some storage like rbd need to wait before release volume - really?
3204 foreach my $volid (@$newvollist) {
3205 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3209 PVE
::Firewall
::remove_vmfw_conf
($newid);
3211 unlink $conffile; # avoid races -> last thing before die
3213 die "clone failed: $err";
3219 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3221 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3224 # Aquire exclusive lock lock for $newid
3225 my $lock_target_vm = sub {
3226 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3229 # exclusive lock if VM is running - else shared lock is enough;
3231 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3233 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3237 __PACKAGE__-
>register_method({
3238 name
=> 'move_vm_disk',
3239 path
=> '{vmid}/move_disk',
3243 description
=> "Move volume to different storage.",
3245 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3247 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3248 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3252 additionalProperties
=> 0,
3254 node
=> get_standard_option
('pve-node'),
3255 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3258 description
=> "The disk you want to move.",
3259 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3261 storage
=> get_standard_option
('pve-storage-id', {
3262 description
=> "Target storage.",
3263 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3267 description
=> "Target Format.",
3268 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3273 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3279 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3284 description
=> "Override I/O bandwidth limit (in KiB/s).",
3288 default => 'move limit from datacenter or storage config',
3294 description
=> "the task ID.",
3299 my $rpcenv = PVE
::RPCEnvironment
::get
();
3300 my $authuser = $rpcenv->get_user();
3302 my $node = extract_param
($param, 'node');
3303 my $vmid = extract_param
($param, 'vmid');
3304 my $digest = extract_param
($param, 'digest');
3305 my $disk = extract_param
($param, 'disk');
3306 my $storeid = extract_param
($param, 'storage');
3307 my $format = extract_param
($param, 'format');
3309 my $storecfg = PVE
::Storage
::config
();
3311 my $updatefn = sub {
3312 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3313 PVE
::QemuConfig-
>check_lock($conf);
3315 die "VM config checksum missmatch (file change by other user?)\n"
3316 if $digest && $digest ne $conf->{digest
};
3318 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3320 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3322 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3323 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3325 my $old_volid = $drive->{file
};
3327 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3328 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3332 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3333 (!$format || !$oldfmt || $oldfmt eq $format);
3335 # this only checks snapshots because $disk is passed!
3336 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3337 die "you can't move a disk with snapshots and delete the source\n"
3338 if $snapshotted && $param->{delete};
3340 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3342 my $running = PVE
::QemuServer
::check_running
($vmid);
3344 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3347 my $newvollist = [];
3353 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3355 warn "moving disk with snapshots, snapshots will not be moved!\n"
3358 my $bwlimit = extract_param
($param, 'bwlimit');
3359 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3361 my $newdrive = PVE
::QemuServer
::clone_disk
(
3379 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3381 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3383 # convert moved disk to base if part of template
3384 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3385 if PVE
::QemuConfig-
>is_template($conf);
3387 PVE
::QemuConfig-
>write_config($vmid, $conf);
3389 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3390 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3391 eval { mon_cmd
($vmid, "guest-fstrim") };
3395 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3396 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3402 foreach my $volid (@$newvollist) {
3403 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3406 die "storage migration failed: $err";
3409 if ($param->{delete}) {
3411 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3412 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3418 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3421 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3424 my $check_vm_disks_local = sub {
3425 my ($storecfg, $vmconf, $vmid) = @_;
3427 my $local_disks = {};
3429 # add some more information to the disks e.g. cdrom
3430 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3431 my ($volid, $attr) = @_;
3433 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3435 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3436 return if $scfg->{shared
};
3438 # The shared attr here is just a special case where the vdisk
3439 # is marked as shared manually
3440 return if $attr->{shared
};
3441 return if $attr->{cdrom
} and $volid eq "none";
3443 if (exists $local_disks->{$volid}) {
3444 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3446 $local_disks->{$volid} = $attr;
3447 # ensure volid is present in case it's needed
3448 $local_disks->{$volid}->{volid
} = $volid;
3452 return $local_disks;
3455 __PACKAGE__-
>register_method({
3456 name
=> 'migrate_vm_precondition',
3457 path
=> '{vmid}/migrate',
3461 description
=> "Get preconditions for migration.",
3463 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3466 additionalProperties
=> 0,
3468 node
=> get_standard_option
('pve-node'),
3469 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3470 target
=> get_standard_option
('pve-node', {
3471 description
=> "Target node.",
3472 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3480 running
=> { type
=> 'boolean' },
3484 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3486 not_allowed_nodes
=> {
3489 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3493 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3495 local_resources
=> {
3497 description
=> "List local resources e.g. pci, usb"
3504 my $rpcenv = PVE
::RPCEnvironment
::get
();
3506 my $authuser = $rpcenv->get_user();
3508 PVE
::Cluster
::check_cfs_quorum
();
3512 my $vmid = extract_param
($param, 'vmid');
3513 my $target = extract_param
($param, 'target');
3514 my $localnode = PVE
::INotify
::nodename
();
3518 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3519 my $storecfg = PVE
::Storage
::config
();
3522 # try to detect errors early
3523 PVE
::QemuConfig-
>check_lock($vmconf);
3525 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3527 # if vm is not running, return target nodes where local storage is available
3528 # for offline migration
3529 if (!$res->{running
}) {
3530 $res->{allowed_nodes
} = [];
3531 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3532 delete $checked_nodes->{$localnode};
3534 foreach my $node (keys %$checked_nodes) {
3535 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3536 push @{$res->{allowed_nodes
}}, $node;
3540 $res->{not_allowed_nodes
} = $checked_nodes;
3544 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3545 $res->{local_disks
} = [ values %$local_disks ];;
3547 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3549 $res->{local_resources
} = $local_resources;
3556 __PACKAGE__-
>register_method({
3557 name
=> 'migrate_vm',
3558 path
=> '{vmid}/migrate',
3562 description
=> "Migrate virtual machine. Creates a new migration task.",
3564 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3567 additionalProperties
=> 0,
3569 node
=> get_standard_option
('pve-node'),
3570 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3571 target
=> get_standard_option
('pve-node', {
3572 description
=> "Target node.",
3573 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3577 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3582 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3587 enum
=> ['secure', 'insecure'],
3588 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3591 migration_network
=> {
3592 type
=> 'string', format
=> 'CIDR',
3593 description
=> "CIDR of the (sub) network that is used for migration.",
3596 "with-local-disks" => {
3598 description
=> "Enable live storage migration for local disk",
3601 targetstorage
=> get_standard_option
('pve-targetstorage', {
3602 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3605 description
=> "Override I/O bandwidth limit (in KiB/s).",
3609 default => 'migrate limit from datacenter or storage config',
3615 description
=> "the task ID.",
3620 my $rpcenv = PVE
::RPCEnvironment
::get
();
3621 my $authuser = $rpcenv->get_user();
3623 my $target = extract_param
($param, 'target');
3625 my $localnode = PVE
::INotify
::nodename
();
3626 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3628 PVE
::Cluster
::check_cfs_quorum
();
3630 PVE
::Cluster
::check_node_exists
($target);
3632 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3634 my $vmid = extract_param
($param, 'vmid');
3636 raise_param_exc
({ force
=> "Only root may use this option." })
3637 if $param->{force
} && $authuser ne 'root@pam';
3639 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3640 if $param->{migration_type
} && $authuser ne 'root@pam';
3642 # allow root only until better network permissions are available
3643 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3644 if $param->{migration_network
} && $authuser ne 'root@pam';
3647 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3649 # try to detect errors early
3651 PVE
::QemuConfig-
>check_lock($conf);
3653 if (PVE
::QemuServer
::check_running
($vmid)) {
3654 die "can't migrate running VM without --online\n" if !$param->{online
};
3656 my $repl_conf = PVE
::ReplicationConfig-
>new();
3657 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3658 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3659 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3660 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3661 "target. Use 'force' to override.\n";
3664 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3665 $param->{online
} = 0;
3668 my $storecfg = PVE
::Storage
::config
();
3670 if (my $targetstorage = $param->{targetstorage
}) {
3671 my $check_storage = sub {
3672 my ($target_sid) = @_;
3673 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3674 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3675 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3676 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3677 if !$scfg->{content
}->{images
};
3680 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3681 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3684 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3685 if !defined($storagemap->{identity
});
3687 foreach my $source (values %{$storagemap->{entries
}}) {
3688 $check_storage->($source);
3691 $check_storage->($storagemap->{default})
3692 if $storagemap->{default};
3694 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3695 if $storagemap->{identity
};
3697 $param->{storagemap
} = $storagemap;
3699 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3702 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3707 print "Requesting HA migration for VM $vmid to node $target\n";
3709 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3710 PVE
::Tools
::run_command
($cmd);
3714 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3719 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3723 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3726 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3731 __PACKAGE__-
>register_method({
3733 path
=> '{vmid}/monitor',
3737 description
=> "Execute Qemu monitor commands.",
3739 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3743 additionalProperties
=> 0,
3745 node
=> get_standard_option
('pve-node'),
3746 vmid
=> get_standard_option
('pve-vmid'),
3749 description
=> "The monitor command.",
3753 returns
=> { type
=> 'string'},
3757 my $rpcenv = PVE
::RPCEnvironment
::get
();
3758 my $authuser = $rpcenv->get_user();
3761 my $command = shift;
3762 return $command =~ m/^\s*info(\s+|$)/
3763 || $command =~ m/^\s*help\s*$/;
3766 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3767 if !&$is_ro($param->{command
});
3769 my $vmid = $param->{vmid
};
3771 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3775 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3777 $res = "ERROR: $@" if $@;
3782 __PACKAGE__-
>register_method({
3783 name
=> 'resize_vm',
3784 path
=> '{vmid}/resize',
3788 description
=> "Extend volume size.",
3790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3793 additionalProperties
=> 0,
3795 node
=> get_standard_option
('pve-node'),
3796 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3797 skiplock
=> get_standard_option
('skiplock'),
3800 description
=> "The disk you want to resize.",
3801 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3805 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3806 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.",
3810 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3816 returns
=> { type
=> 'null'},
3820 my $rpcenv = PVE
::RPCEnvironment
::get
();
3822 my $authuser = $rpcenv->get_user();
3824 my $node = extract_param
($param, 'node');
3826 my $vmid = extract_param
($param, 'vmid');
3828 my $digest = extract_param
($param, 'digest');
3830 my $disk = extract_param
($param, 'disk');
3832 my $sizestr = extract_param
($param, 'size');
3834 my $skiplock = extract_param
($param, 'skiplock');
3835 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3836 if $skiplock && $authuser ne 'root@pam';
3838 my $storecfg = PVE
::Storage
::config
();
3840 my $updatefn = sub {
3842 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3844 die "checksum missmatch (file change by other user?)\n"
3845 if $digest && $digest ne $conf->{digest
};
3846 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3848 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3850 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3852 my (undef, undef, undef, undef, undef, undef, $format) =
3853 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3855 die "can't resize volume: $disk if snapshot exists\n"
3856 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3858 my $volid = $drive->{file
};
3860 die "disk '$disk' has no associated volume\n" if !$volid;
3862 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3864 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3866 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3868 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3869 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3871 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3873 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3874 my ($ext, $newsize, $unit) = ($1, $2, $4);
3877 $newsize = $newsize * 1024;
3878 } elsif ($unit eq 'M') {
3879 $newsize = $newsize * 1024 * 1024;
3880 } elsif ($unit eq 'G') {
3881 $newsize = $newsize * 1024 * 1024 * 1024;
3882 } elsif ($unit eq 'T') {
3883 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3886 $newsize += $size if $ext;
3887 $newsize = int($newsize);
3889 die "shrinking disks is not supported\n" if $newsize < $size;
3891 return if $size == $newsize;
3893 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3895 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3897 $drive->{size
} = $newsize;
3898 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3900 PVE
::QemuConfig-
>write_config($vmid, $conf);
3903 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3907 __PACKAGE__-
>register_method({
3908 name
=> 'snapshot_list',
3909 path
=> '{vmid}/snapshot',
3911 description
=> "List all snapshots.",
3913 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3916 protected
=> 1, # qemu pid files are only readable by root
3918 additionalProperties
=> 0,
3920 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3921 node
=> get_standard_option
('pve-node'),
3930 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3934 description
=> "Snapshot includes RAM.",
3939 description
=> "Snapshot description.",
3943 description
=> "Snapshot creation time",
3945 renderer
=> 'timestamp',
3949 description
=> "Parent snapshot identifier.",
3955 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3960 my $vmid = $param->{vmid
};
3962 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3963 my $snaphash = $conf->{snapshots
} || {};
3967 foreach my $name (keys %$snaphash) {
3968 my $d = $snaphash->{$name};
3971 snaptime
=> $d->{snaptime
} || 0,
3972 vmstate
=> $d->{vmstate
} ?
1 : 0,
3973 description
=> $d->{description
} || '',
3975 $item->{parent
} = $d->{parent
} if $d->{parent
};
3976 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3980 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3983 digest
=> $conf->{digest
},
3984 running
=> $running,
3985 description
=> "You are here!",
3987 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3989 push @$res, $current;
3994 __PACKAGE__-
>register_method({
3996 path
=> '{vmid}/snapshot',
4000 description
=> "Snapshot a VM.",
4002 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4005 additionalProperties
=> 0,
4007 node
=> get_standard_option
('pve-node'),
4008 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4009 snapname
=> get_standard_option
('pve-snapshot-name'),
4013 description
=> "Save the vmstate",
4018 description
=> "A textual description or comment.",
4024 description
=> "the task ID.",
4029 my $rpcenv = PVE
::RPCEnvironment
::get
();
4031 my $authuser = $rpcenv->get_user();
4033 my $node = extract_param
($param, 'node');
4035 my $vmid = extract_param
($param, 'vmid');
4037 my $snapname = extract_param
($param, 'snapname');
4039 die "unable to use snapshot name 'current' (reserved name)\n"
4040 if $snapname eq 'current';
4042 die "unable to use snapshot name 'pending' (reserved name)\n"
4043 if lc($snapname) eq 'pending';
4046 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4047 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4048 $param->{description
});
4051 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4054 __PACKAGE__-
>register_method({
4055 name
=> 'snapshot_cmd_idx',
4056 path
=> '{vmid}/snapshot/{snapname}',
4063 additionalProperties
=> 0,
4065 vmid
=> get_standard_option
('pve-vmid'),
4066 node
=> get_standard_option
('pve-node'),
4067 snapname
=> get_standard_option
('pve-snapshot-name'),
4076 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4083 push @$res, { cmd
=> 'rollback' };
4084 push @$res, { cmd
=> 'config' };
4089 __PACKAGE__-
>register_method({
4090 name
=> 'update_snapshot_config',
4091 path
=> '{vmid}/snapshot/{snapname}/config',
4095 description
=> "Update snapshot metadata.",
4097 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4100 additionalProperties
=> 0,
4102 node
=> get_standard_option
('pve-node'),
4103 vmid
=> get_standard_option
('pve-vmid'),
4104 snapname
=> get_standard_option
('pve-snapshot-name'),
4108 description
=> "A textual description or comment.",
4112 returns
=> { type
=> 'null' },
4116 my $rpcenv = PVE
::RPCEnvironment
::get
();
4118 my $authuser = $rpcenv->get_user();
4120 my $vmid = extract_param
($param, 'vmid');
4122 my $snapname = extract_param
($param, 'snapname');
4124 return if !defined($param->{description
});
4126 my $updatefn = sub {
4128 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4130 PVE
::QemuConfig-
>check_lock($conf);
4132 my $snap = $conf->{snapshots
}->{$snapname};
4134 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4136 $snap->{description
} = $param->{description
} if defined($param->{description
});
4138 PVE
::QemuConfig-
>write_config($vmid, $conf);
4141 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4146 __PACKAGE__-
>register_method({
4147 name
=> 'get_snapshot_config',
4148 path
=> '{vmid}/snapshot/{snapname}/config',
4151 description
=> "Get snapshot configuration",
4153 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4156 additionalProperties
=> 0,
4158 node
=> get_standard_option
('pve-node'),
4159 vmid
=> get_standard_option
('pve-vmid'),
4160 snapname
=> get_standard_option
('pve-snapshot-name'),
4163 returns
=> { type
=> "object" },
4167 my $rpcenv = PVE
::RPCEnvironment
::get
();
4169 my $authuser = $rpcenv->get_user();
4171 my $vmid = extract_param
($param, 'vmid');
4173 my $snapname = extract_param
($param, 'snapname');
4175 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4177 my $snap = $conf->{snapshots
}->{$snapname};
4179 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4184 __PACKAGE__-
>register_method({
4186 path
=> '{vmid}/snapshot/{snapname}/rollback',
4190 description
=> "Rollback VM state to specified snapshot.",
4192 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4195 additionalProperties
=> 0,
4197 node
=> get_standard_option
('pve-node'),
4198 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4199 snapname
=> get_standard_option
('pve-snapshot-name'),
4204 description
=> "the task ID.",
4209 my $rpcenv = PVE
::RPCEnvironment
::get
();
4211 my $authuser = $rpcenv->get_user();
4213 my $node = extract_param
($param, 'node');
4215 my $vmid = extract_param
($param, 'vmid');
4217 my $snapname = extract_param
($param, 'snapname');
4220 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4221 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4225 # hold migration lock, this makes sure that nobody create replication snapshots
4226 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4229 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4232 __PACKAGE__-
>register_method({
4233 name
=> 'delsnapshot',
4234 path
=> '{vmid}/snapshot/{snapname}',
4238 description
=> "Delete a VM snapshot.",
4240 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4243 additionalProperties
=> 0,
4245 node
=> get_standard_option
('pve-node'),
4246 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4247 snapname
=> get_standard_option
('pve-snapshot-name'),
4251 description
=> "For removal from config file, even if removing disk snapshots fails.",
4257 description
=> "the task ID.",
4262 my $rpcenv = PVE
::RPCEnvironment
::get
();
4264 my $authuser = $rpcenv->get_user();
4266 my $node = extract_param
($param, 'node');
4268 my $vmid = extract_param
($param, 'vmid');
4270 my $snapname = extract_param
($param, 'snapname');
4273 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4274 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4277 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4280 __PACKAGE__-
>register_method({
4282 path
=> '{vmid}/template',
4286 description
=> "Create a Template.",
4288 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4289 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4292 additionalProperties
=> 0,
4294 node
=> get_standard_option
('pve-node'),
4295 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4299 description
=> "If you want to convert only 1 disk to base image.",
4300 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4305 returns
=> { type
=> 'null'},
4309 my $rpcenv = PVE
::RPCEnvironment
::get
();
4311 my $authuser = $rpcenv->get_user();
4313 my $node = extract_param
($param, 'node');
4315 my $vmid = extract_param
($param, 'vmid');
4317 my $disk = extract_param
($param, 'disk');
4319 my $updatefn = sub {
4321 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4323 PVE
::QemuConfig-
>check_lock($conf);
4325 die "unable to create template, because VM contains snapshots\n"
4326 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4328 die "you can't convert a template to a template\n"
4329 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4331 die "you can't convert a VM to template if VM is running\n"
4332 if PVE
::QemuServer
::check_running
($vmid);
4335 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4338 $conf->{template
} = 1;
4339 PVE
::QemuConfig-
>write_config($vmid, $conf);
4341 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4344 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4348 __PACKAGE__-
>register_method({
4349 name
=> 'cloudinit_generated_config_dump',
4350 path
=> '{vmid}/cloudinit/dump',
4353 description
=> "Get automatically generated cloudinit config.",
4355 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4358 additionalProperties
=> 0,
4360 node
=> get_standard_option
('pve-node'),
4361 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4363 description
=> 'Config type.',
4365 enum
=> ['user', 'network', 'meta'],
4375 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4377 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});