1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::RPCEnvironment
;
19 use PVE
::AccessControl
;
22 use PVE
::API2
::Firewall
::VM
;
25 use Data
::Dumper
; # fixme: remove
27 use base
qw(PVE::RESTHandler);
29 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.";
31 my $resolve_cdrom_alias = sub {
34 if (my $value = $param->{cdrom
}) {
35 $value .= ",media=cdrom" if $value !~ m/media=/;
36 $param->{ide2
} = $value;
37 delete $param->{cdrom
};
41 my $test_deallocate_drive = sub {
42 my ($storecfg, $vmid, $key, $drive, $force) = @_;
44 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
45 my $volid = $drive->{file
};
46 if ( PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
47 if ($force || $key =~ m/^unused/) {
48 my $sid = PVE
::Storage
::parse_volume_id
($volid);
57 my $check_storage_access = sub {
58 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
60 PVE
::QemuServer
::foreach_drive
($settings, sub {
61 my ($ds, $drive) = @_;
63 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
65 my $volid = $drive->{file
};
67 if (!$volid || $volid eq 'none') {
69 } elsif ($isCDROM && ($volid eq 'cdrom')) {
70 $rpcenv->check($authuser, "/", ['Sys.Console']);
71 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
72 my ($storeid, $size) = ($2 || $default_storage, $3);
73 die "no storage ID specified (and no default storage)\n" if !$storeid;
74 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
76 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
81 my $check_storage_access_clone = sub {
82 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
86 PVE
::QemuServer
::foreach_drive
($conf, sub {
87 my ($ds, $drive) = @_;
89 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
91 my $volid = $drive->{file
};
93 return if !$volid || $volid eq 'none';
96 if ($volid eq 'cdrom') {
97 $rpcenv->check($authuser, "/", ['Sys.Console']);
99 # we simply allow access
100 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
101 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
102 $sharedvm = 0 if !$scfg->{shared
};
106 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
107 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
108 $sharedvm = 0 if !$scfg->{shared
};
110 $sid = $storage if $storage;
111 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
118 # Note: $pool is only needed when creating a VM, because pool permissions
119 # are automatically inherited if VM already exists inside a pool.
120 my $create_disks = sub {
121 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
126 PVE
::QemuServer
::foreach_drive
($settings, sub {
127 my ($ds, $disk) = @_;
129 my $volid = $disk->{file
};
131 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
132 delete $disk->{size
};
133 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
134 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
135 my ($storeid, $size) = ($2 || $default_storage, $3);
136 die "no storage ID specified (and no default storage)\n" if !$storeid;
137 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
138 my $fmt = $disk->{format
} || $defformat;
139 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
140 $fmt, undef, $size*1024*1024);
141 $disk->{file
} = $volid;
142 $disk->{size
} = $size*1024*1024*1024;
143 push @$vollist, $volid;
144 delete $disk->{format
}; # no longer needed
145 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
148 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
150 my $volid_is_new = 1;
153 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
154 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
159 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
161 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
163 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
165 die "volume $volid does not exists\n" if !$size;
167 $disk->{size
} = $size;
170 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
174 # free allocated images on error
176 syslog
('err', "VM $vmid creating disks failed");
177 foreach my $volid (@$vollist) {
178 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
184 # modify vm config if everything went well
185 foreach my $ds (keys %$res) {
186 $conf->{$ds} = $res->{$ds};
192 my $delete_drive = sub {
193 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
195 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
196 my $volid = $drive->{file
};
198 if (PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
199 if ($force || $key =~ m/^unused/) {
201 # check if the disk is really unused
202 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
203 my $path = PVE
::Storage
::path
($storecfg, $volid);
205 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
206 if $used_paths->{$path};
208 PVE
::Storage
::vdisk_free
($storecfg, $volid);
212 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
217 delete $conf->{$key};
220 my $check_vm_modify_config_perm = sub {
221 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
223 return 1 if $authuser eq 'root@pam';
225 foreach my $opt (@$key_list) {
226 # disk checks need to be done somewhere else
227 next if PVE
::QemuServer
::valid_drivename
($opt);
229 if ($opt eq 'sockets' || $opt eq 'cores' ||
230 $opt eq 'cpu' || $opt eq 'smp' || $opt eq 'vcpus' ||
231 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
232 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
233 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
234 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
235 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
236 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
237 } elsif ($opt eq 'args' || $opt eq 'lock') {
238 die "only root can set '$opt' config\n";
239 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
240 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
241 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
242 } elsif ($opt =~ m/^net\d+$/) {
243 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
245 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
252 __PACKAGE__-
>register_method({
256 description
=> "Virtual machine index (per node).",
258 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
262 protected
=> 1, # qemu pid files are only readable by root
264 additionalProperties
=> 0,
266 node
=> get_standard_option
('pve-node'),
275 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
280 my $rpcenv = PVE
::RPCEnvironment
::get
();
281 my $authuser = $rpcenv->get_user();
283 my $vmstatus = PVE
::QemuServer
::vmstatus
();
286 foreach my $vmid (keys %$vmstatus) {
287 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
289 my $data = $vmstatus->{$vmid};
290 $data->{vmid
} = int($vmid);
299 __PACKAGE__-
>register_method({
303 description
=> "Create or restore a virtual machine.",
305 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
306 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
307 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
308 user
=> 'all', # check inside
313 additionalProperties
=> 0,
314 properties
=> PVE
::QemuServer
::json_config_properties
(
316 node
=> get_standard_option
('pve-node'),
317 vmid
=> get_standard_option
('pve-vmid'),
319 description
=> "The backup file.",
324 storage
=> get_standard_option
('pve-storage-id', {
325 description
=> "Default storage.",
331 description
=> "Allow to overwrite existing VM.",
332 requires
=> 'archive',
337 description
=> "Assign a unique random ethernet address.",
338 requires
=> 'archive',
342 type
=> 'string', format
=> 'pve-poolid',
343 description
=> "Add the VM to the specified pool.",
353 my $rpcenv = PVE
::RPCEnvironment
::get
();
355 my $authuser = $rpcenv->get_user();
357 my $node = extract_param
($param, 'node');
359 my $vmid = extract_param
($param, 'vmid');
361 my $archive = extract_param
($param, 'archive');
363 my $storage = extract_param
($param, 'storage');
365 my $force = extract_param
($param, 'force');
367 my $unique = extract_param
($param, 'unique');
369 my $pool = extract_param
($param, 'pool');
371 my $filename = PVE
::QemuServer
::config_file
($vmid);
373 my $storecfg = PVE
::Storage
::config
();
375 PVE
::Cluster
::check_cfs_quorum
();
377 if (defined($pool)) {
378 $rpcenv->check_pool_exist($pool);
381 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
382 if defined($storage);
384 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
386 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
388 } elsif ($archive && $force && (-f
$filename) &&
389 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
390 # OK: user has VM.Backup permissions, and want to restore an existing VM
396 &$resolve_cdrom_alias($param);
398 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
400 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
402 foreach my $opt (keys %$param) {
403 if (PVE
::QemuServer
::valid_drivename
($opt)) {
404 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
405 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
407 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
408 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
412 PVE
::QemuServer
::add_random_macs
($param);
414 my $keystr = join(' ', keys %$param);
415 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
417 if ($archive eq '-') {
418 die "pipe requires cli environment\n"
419 if $rpcenv->{type
} ne 'cli';
421 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
422 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
426 my $restorefn = sub {
428 # fixme: this test does not work if VM exists on other node!
430 die "unable to restore vm $vmid: config file already exists\n"
433 die "unable to restore vm $vmid: vm is running\n"
434 if PVE
::QemuServer
::check_running
($vmid);
438 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
441 unique
=> $unique });
443 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
446 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
452 die "unable to create vm $vmid: config file already exists\n"
463 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
465 # try to be smart about bootdisk
466 my @disks = PVE
::QemuServer
::disknames
();
468 foreach my $ds (reverse @disks) {
469 next if !$conf->{$ds};
470 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
471 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
475 if (!$conf->{bootdisk
} && $firstdisk) {
476 $conf->{bootdisk
} = $firstdisk;
479 # auto generate uuid if user did not specify smbios1 option
480 if (!$conf->{smbios1
}) {
481 my ($uuid, $uuid_str);
482 UUID
::generate
($uuid);
483 UUID
::unparse
($uuid, $uuid_str);
484 $conf->{smbios1
} = "uuid=$uuid_str";
487 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
493 foreach my $volid (@$vollist) {
494 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
497 die "create failed - $err";
500 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
503 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
506 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
509 __PACKAGE__-
>register_method({
514 description
=> "Directory index",
519 additionalProperties
=> 0,
521 node
=> get_standard_option
('pve-node'),
522 vmid
=> get_standard_option
('pve-vmid'),
530 subdir
=> { type
=> 'string' },
533 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
539 { subdir
=> 'config' },
540 { subdir
=> 'pending' },
541 { subdir
=> 'status' },
542 { subdir
=> 'unlink' },
543 { subdir
=> 'vncproxy' },
544 { subdir
=> 'migrate' },
545 { subdir
=> 'resize' },
546 { subdir
=> 'move' },
548 { subdir
=> 'rrddata' },
549 { subdir
=> 'monitor' },
550 { subdir
=> 'snapshot' },
551 { subdir
=> 'spiceproxy' },
552 { subdir
=> 'sendkey' },
553 { subdir
=> 'firewall' },
559 __PACKAGE__-
>register_method ({
560 subclass
=> "PVE::API2::Firewall::VM",
561 path
=> '{vmid}/firewall',
564 __PACKAGE__-
>register_method({
566 path
=> '{vmid}/rrd',
568 protected
=> 1, # fixme: can we avoid that?
570 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
572 description
=> "Read VM RRD statistics (returns PNG)",
574 additionalProperties
=> 0,
576 node
=> get_standard_option
('pve-node'),
577 vmid
=> get_standard_option
('pve-vmid'),
579 description
=> "Specify the time frame you are interested in.",
581 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
584 description
=> "The list of datasources you want to display.",
585 type
=> 'string', format
=> 'pve-configid-list',
588 description
=> "The RRD consolidation function",
590 enum
=> [ 'AVERAGE', 'MAX' ],
598 filename
=> { type
=> 'string' },
604 return PVE
::Cluster
::create_rrd_graph
(
605 "pve2-vm/$param->{vmid}", $param->{timeframe
},
606 $param->{ds
}, $param->{cf
});
610 __PACKAGE__-
>register_method({
612 path
=> '{vmid}/rrddata',
614 protected
=> 1, # fixme: can we avoid that?
616 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
618 description
=> "Read VM RRD statistics",
620 additionalProperties
=> 0,
622 node
=> get_standard_option
('pve-node'),
623 vmid
=> get_standard_option
('pve-vmid'),
625 description
=> "Specify the time frame you are interested in.",
627 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
630 description
=> "The RRD consolidation function",
632 enum
=> [ 'AVERAGE', 'MAX' ],
647 return PVE
::Cluster
::create_rrd_data
(
648 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
652 __PACKAGE__-
>register_method({
654 path
=> '{vmid}/config',
657 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
659 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
662 additionalProperties
=> 0,
664 node
=> get_standard_option
('pve-node'),
665 vmid
=> get_standard_option
('pve-vmid'),
667 description
=> "Get current values (instead of pending values).",
679 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
686 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
688 delete $conf->{snapshots
};
690 if (!$param->{current
}) {
691 foreach my $opt (keys %{$conf->{pending
}}) {
692 next if $opt eq 'delete';
693 my $value = $conf->{pending
}->{$opt};
694 next if ref($value); # just to be sure
695 $conf->{$opt} = $value;
697 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
698 delete $conf->{$opt} if $conf->{$opt};
702 delete $conf->{pending
};
707 __PACKAGE__-
>register_method({
708 name
=> 'vm_pending',
709 path
=> '{vmid}/pending',
712 description
=> "Get virtual machine configuration, including pending changes.",
714 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
717 additionalProperties
=> 0,
719 node
=> get_standard_option
('pve-node'),
720 vmid
=> get_standard_option
('pve-vmid'),
729 description
=> "Configuration option name.",
733 description
=> "Current value.",
738 description
=> "Pending value.",
743 description
=> "Indicated a pending delete request.",
753 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
755 my $pending_delete_hash = {};
756 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
757 $pending_delete_hash->{$opt} = 1;
762 foreach my $opt (keys %$conf) {
763 next if ref($conf->{$opt});
764 my $item = { key
=> $opt };
765 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
766 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
767 $item->{delete} = 1 if $pending_delete_hash->{$opt};
771 foreach my $opt (keys %{$conf->{pending
}}) {
772 next if $opt eq 'delete';
773 next if ref($conf->{pending
}->{$opt}); # just to be sure
774 next if defined($conf->{$opt});
775 my $item = { key
=> $opt };
776 $item->{pending
} = $conf->{pending
}->{$opt};
780 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
781 next if $conf->{pending
}->{$opt}; # just to be sure
782 next if $conf->{$opt};
783 my $item = { key
=> $opt, delete => 1};
790 # POST/PUT {vmid}/config implementation
792 # The original API used PUT (idempotent) an we assumed that all operations
793 # are fast. But it turned out that almost any configuration change can
794 # involve hot-plug actions, or disk alloc/free. Such actions can take long
795 # time to complete and have side effects (not idempotent).
797 # The new implementation uses POST and forks a worker process. We added
798 # a new option 'background_delay'. If specified we wait up to
799 # 'background_delay' second for the worker task to complete. It returns null
800 # if the task is finished within that time, else we return the UPID.
802 my $update_vm_api = sub {
803 my ($param, $sync) = @_;
805 my $rpcenv = PVE
::RPCEnvironment
::get
();
807 my $authuser = $rpcenv->get_user();
809 my $node = extract_param
($param, 'node');
811 my $vmid = extract_param
($param, 'vmid');
813 my $digest = extract_param
($param, 'digest');
815 my $background_delay = extract_param
($param, 'background_delay');
817 my @paramarr = (); # used for log message
818 foreach my $key (keys %$param) {
819 push @paramarr, "-$key", $param->{$key};
822 my $skiplock = extract_param
($param, 'skiplock');
823 raise_param_exc
({ skiplock
=> "Only root may use this option." })
824 if $skiplock && $authuser ne 'root@pam';
826 my $delete_str = extract_param
($param, 'delete');
828 my $revert_str = extract_param
($param, 'revert');
830 my $force = extract_param
($param, 'force');
832 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
834 my $storecfg = PVE
::Storage
::config
();
836 my $defaults = PVE
::QemuServer
::load_defaults
();
838 &$resolve_cdrom_alias($param);
840 # now try to verify all parameters
843 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
844 if (!PVE
::QemuServer
::option_exists
($opt)) {
845 raise_param_exc
({ revert
=> "unknown option '$opt'" });
848 raise_param_exc
({ delete => "you can't use '-$opt' and " .
849 "-revert $opt' at the same time" })
850 if defined($param->{$opt});
856 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
857 $opt = 'ide2' if $opt eq 'cdrom';
859 raise_param_exc
({ delete => "you can't use '-$opt' and " .
860 "-delete $opt' at the same time" })
861 if defined($param->{$opt});
863 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
864 "-revert $opt' at the same time" })
867 if (!PVE
::QemuServer
::option_exists
($opt)) {
868 raise_param_exc
({ delete => "unknown option '$opt'" });
874 foreach my $opt (keys %$param) {
875 if (PVE
::QemuServer
::valid_drivename
($opt)) {
877 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
878 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
879 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
880 } elsif ($opt =~ m/^net(\d+)$/) {
882 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
883 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
887 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
889 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
891 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
895 my $conf = PVE
::QemuServer
::load_config
($vmid);
897 die "checksum missmatch (file change by other user?)\n"
898 if $digest && $digest ne $conf->{digest
};
900 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
902 foreach my $opt (keys %$revert) {
903 if (defined($conf->{$opt})) {
904 $param->{$opt} = $conf->{$opt};
905 } elsif (defined($conf->{pending
}->{$opt})) {
910 if ($param->{memory
} || defined($param->{balloon
})) {
911 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
912 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
914 die "balloon value too large (must be smaller than assigned memory)\n"
915 if $balloon && $balloon > $maxmem;
918 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
922 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
924 # write updates to pending section
926 my $modified = {}; # record what $option we modify
928 foreach my $opt (@delete) {
929 $modified->{$opt} = 1;
930 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
931 if ($opt =~ m/^unused/) {
932 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
933 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
934 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
935 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
936 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
937 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
939 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
940 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
941 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
942 if defined($conf->{pending
}->{$opt});
943 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
944 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
946 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
947 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
951 foreach my $opt (keys %$param) { # add/change
952 $modified->{$opt} = 1;
953 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
954 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
956 if (PVE
::QemuServer
::valid_drivename
($opt)) {
957 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
958 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
959 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
961 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
963 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
964 if defined($conf->{pending
}->{$opt});
966 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
968 $conf->{pending
}->{$opt} = $param->{$opt};
970 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
971 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
974 # remove pending changes when nothing changed
975 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
976 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
977 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
979 return if !scalar(keys %{$conf->{pending
}});
981 my $running = PVE
::QemuServer
::check_running
($vmid);
983 # apply pending changes
985 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
989 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
990 raise_param_exc
($errors) if scalar(keys %$errors);
992 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1002 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1004 if ($background_delay) {
1006 # Note: It would be better to do that in the Event based HTTPServer
1007 # to avoid blocking call to sleep.
1009 my $end_time = time() + $background_delay;
1011 my $task = PVE
::Tools
::upid_decode
($upid);
1014 while (time() < $end_time) {
1015 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1017 sleep(1); # this gets interrupted when child process ends
1021 my $status = PVE
::Tools
::upid_read_status
($upid);
1022 return undef if $status eq 'OK';
1031 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1034 my $vm_config_perm_list = [
1039 'VM.Config.Network',
1041 'VM.Config.Options',
1044 __PACKAGE__-
>register_method({
1045 name
=> 'update_vm_async',
1046 path
=> '{vmid}/config',
1050 description
=> "Set virtual machine options (asynchrounous API).",
1052 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1055 additionalProperties
=> 0,
1056 properties
=> PVE
::QemuServer
::json_config_properties
(
1058 node
=> get_standard_option
('pve-node'),
1059 vmid
=> get_standard_option
('pve-vmid'),
1060 skiplock
=> get_standard_option
('skiplock'),
1062 type
=> 'string', format
=> 'pve-configid-list',
1063 description
=> "A list of settings you want to delete.",
1067 type
=> 'string', format
=> 'pve-configid-list',
1068 description
=> "Revert a pending change.",
1073 description
=> $opt_force_description,
1075 requires
=> 'delete',
1079 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1083 background_delay
=> {
1085 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1096 code
=> $update_vm_api,
1099 __PACKAGE__-
>register_method({
1100 name
=> 'update_vm',
1101 path
=> '{vmid}/config',
1105 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1107 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1110 additionalProperties
=> 0,
1111 properties
=> PVE
::QemuServer
::json_config_properties
(
1113 node
=> get_standard_option
('pve-node'),
1114 vmid
=> get_standard_option
('pve-vmid'),
1115 skiplock
=> get_standard_option
('skiplock'),
1117 type
=> 'string', format
=> 'pve-configid-list',
1118 description
=> "A list of settings you want to delete.",
1122 type
=> 'string', format
=> 'pve-configid-list',
1123 description
=> "Revert a pending change.",
1128 description
=> $opt_force_description,
1130 requires
=> 'delete',
1134 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1140 returns
=> { type
=> 'null' },
1143 &$update_vm_api($param, 1);
1149 __PACKAGE__-
>register_method({
1150 name
=> 'destroy_vm',
1155 description
=> "Destroy the vm (also delete all used/owned volumes).",
1157 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1160 additionalProperties
=> 0,
1162 node
=> get_standard_option
('pve-node'),
1163 vmid
=> get_standard_option
('pve-vmid'),
1164 skiplock
=> get_standard_option
('skiplock'),
1173 my $rpcenv = PVE
::RPCEnvironment
::get
();
1175 my $authuser = $rpcenv->get_user();
1177 my $vmid = $param->{vmid
};
1179 my $skiplock = $param->{skiplock
};
1180 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1181 if $skiplock && $authuser ne 'root@pam';
1184 my $conf = PVE
::QemuServer
::load_config
($vmid);
1186 my $storecfg = PVE
::Storage
::config
();
1188 my $delVMfromPoolFn = sub {
1189 my $usercfg = cfs_read_file
("user.cfg");
1190 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1191 if (my $data = $usercfg->{pools
}->{$pool}) {
1192 delete $data->{vms
}->{$vmid};
1193 delete $usercfg->{vms
}->{$vmid};
1194 cfs_write_file
("user.cfg", $usercfg);
1202 syslog
('info', "destroy VM $vmid: $upid\n");
1204 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1206 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1209 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1212 __PACKAGE__-
>register_method({
1214 path
=> '{vmid}/unlink',
1218 description
=> "Unlink/delete disk images.",
1220 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1223 additionalProperties
=> 0,
1225 node
=> get_standard_option
('pve-node'),
1226 vmid
=> get_standard_option
('pve-vmid'),
1228 type
=> 'string', format
=> 'pve-configid-list',
1229 description
=> "A list of disk IDs you want to delete.",
1233 description
=> $opt_force_description,
1238 returns
=> { type
=> 'null'},
1242 $param->{delete} = extract_param
($param, 'idlist');
1244 __PACKAGE__-
>update_vm($param);
1251 __PACKAGE__-
>register_method({
1253 path
=> '{vmid}/vncproxy',
1257 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1259 description
=> "Creates a TCP VNC proxy connections.",
1261 additionalProperties
=> 0,
1263 node
=> get_standard_option
('pve-node'),
1264 vmid
=> get_standard_option
('pve-vmid'),
1268 description
=> "starts websockify instead of vncproxy",
1273 additionalProperties
=> 0,
1275 user
=> { type
=> 'string' },
1276 ticket
=> { type
=> 'string' },
1277 cert
=> { type
=> 'string' },
1278 port
=> { type
=> 'integer' },
1279 upid
=> { type
=> 'string' },
1285 my $rpcenv = PVE
::RPCEnvironment
::get
();
1287 my $authuser = $rpcenv->get_user();
1289 my $vmid = $param->{vmid
};
1290 my $node = $param->{node
};
1291 my $websocket = $param->{websocket
};
1293 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1295 my $authpath = "/vms/$vmid";
1297 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1299 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1302 my $port = PVE
::Tools
::next_vnc_port
();
1307 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1308 $remip = PVE
::Cluster
::remote_node_ip
($node);
1309 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1310 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1318 syslog
('info', "starting vnc proxy $upid\n");
1322 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1324 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1326 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1327 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1328 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1329 '-timeout', $timeout, '-authpath', $authpath,
1330 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1333 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1335 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1337 my $qmstr = join(' ', @$qmcmd);
1339 # also redirect stderr (else we get RFB protocol errors)
1340 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1343 PVE
::Tools
::run_command
($cmd);
1348 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1350 PVE
::Tools
::wait_for_vnc_port
($port);
1361 __PACKAGE__-
>register_method({
1362 name
=> 'vncwebsocket',
1363 path
=> '{vmid}/vncwebsocket',
1366 description
=> "You also need to pass a valid ticket (vncticket).",
1367 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1369 description
=> "Opens a weksocket for VNC traffic.",
1371 additionalProperties
=> 0,
1373 node
=> get_standard_option
('pve-node'),
1374 vmid
=> get_standard_option
('pve-vmid'),
1376 description
=> "Ticket from previous call to vncproxy.",
1381 description
=> "Port number returned by previous vncproxy call.",
1391 port
=> { type
=> 'string' },
1397 my $rpcenv = PVE
::RPCEnvironment
::get
();
1399 my $authuser = $rpcenv->get_user();
1401 my $vmid = $param->{vmid
};
1402 my $node = $param->{node
};
1404 my $authpath = "/vms/$vmid";
1406 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1408 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1410 # Note: VNC ports are acessible from outside, so we do not gain any
1411 # security if we verify that $param->{port} belongs to VM $vmid. This
1412 # check is done by verifying the VNC ticket (inside VNC protocol).
1414 my $port = $param->{port
};
1416 return { port
=> $port };
1419 __PACKAGE__-
>register_method({
1420 name
=> 'spiceproxy',
1421 path
=> '{vmid}/spiceproxy',
1426 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1428 description
=> "Returns a SPICE configuration to connect to the VM.",
1430 additionalProperties
=> 0,
1432 node
=> get_standard_option
('pve-node'),
1433 vmid
=> get_standard_option
('pve-vmid'),
1434 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1437 returns
=> get_standard_option
('remote-viewer-config'),
1441 my $rpcenv = PVE
::RPCEnvironment
::get
();
1443 my $authuser = $rpcenv->get_user();
1445 my $vmid = $param->{vmid
};
1446 my $node = $param->{node
};
1447 my $proxy = $param->{proxy
};
1449 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1450 my $title = "VM $vmid - $conf->{'name'}",
1452 my $port = PVE
::QemuServer
::spice_port
($vmid);
1454 my ($ticket, undef, $remote_viewer_config) =
1455 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1457 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1458 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1460 return $remote_viewer_config;
1463 __PACKAGE__-
>register_method({
1465 path
=> '{vmid}/status',
1468 description
=> "Directory index",
1473 additionalProperties
=> 0,
1475 node
=> get_standard_option
('pve-node'),
1476 vmid
=> get_standard_option
('pve-vmid'),
1484 subdir
=> { type
=> 'string' },
1487 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1493 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1496 { subdir
=> 'current' },
1497 { subdir
=> 'start' },
1498 { subdir
=> 'stop' },
1504 __PACKAGE__-
>register_method({
1505 name
=> 'vm_status',
1506 path
=> '{vmid}/status/current',
1509 protected
=> 1, # qemu pid files are only readable by root
1510 description
=> "Get virtual machine status.",
1512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1515 additionalProperties
=> 0,
1517 node
=> get_standard_option
('pve-node'),
1518 vmid
=> get_standard_option
('pve-vmid'),
1521 returns
=> { type
=> 'object' },
1526 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1528 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1529 my $status = $vmstatus->{$param->{vmid
}};
1531 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1533 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1538 __PACKAGE__-
>register_method({
1540 path
=> '{vmid}/status/start',
1544 description
=> "Start virtual machine.",
1546 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1549 additionalProperties
=> 0,
1551 node
=> get_standard_option
('pve-node'),
1552 vmid
=> get_standard_option
('pve-vmid'),
1553 skiplock
=> get_standard_option
('skiplock'),
1554 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1555 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1556 machine
=> get_standard_option
('pve-qm-machine'),
1565 my $rpcenv = PVE
::RPCEnvironment
::get
();
1567 my $authuser = $rpcenv->get_user();
1569 my $node = extract_param
($param, 'node');
1571 my $vmid = extract_param
($param, 'vmid');
1573 my $machine = extract_param
($param, 'machine');
1575 my $stateuri = extract_param
($param, 'stateuri');
1576 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1577 if $stateuri && $authuser ne 'root@pam';
1579 my $skiplock = extract_param
($param, 'skiplock');
1580 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1581 if $skiplock && $authuser ne 'root@pam';
1583 my $migratedfrom = extract_param
($param, 'migratedfrom');
1584 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1585 if $migratedfrom && $authuser ne 'root@pam';
1587 # read spice ticket from STDIN
1589 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1590 if (defined(my $line = <>)) {
1592 $spice_ticket = $line;
1596 my $storecfg = PVE
::Storage
::config
();
1598 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1599 $rpcenv->{type
} ne 'ha') {
1604 my $service = "vm:$vmid";
1606 my $cmd = ['ha-manager', 'enable', $service];
1608 print "Executing HA start for VM $vmid\n";
1610 PVE
::Tools
::run_command
($cmd);
1615 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1622 syslog
('info', "start VM $vmid: $upid\n");
1624 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1625 $machine, $spice_ticket);
1630 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1634 __PACKAGE__-
>register_method({
1636 path
=> '{vmid}/status/stop',
1640 description
=> "Stop virtual machine.",
1642 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1645 additionalProperties
=> 0,
1647 node
=> get_standard_option
('pve-node'),
1648 vmid
=> get_standard_option
('pve-vmid'),
1649 skiplock
=> get_standard_option
('skiplock'),
1650 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1652 description
=> "Wait maximal timeout seconds.",
1658 description
=> "Do not decativate storage volumes.",
1671 my $rpcenv = PVE
::RPCEnvironment
::get
();
1673 my $authuser = $rpcenv->get_user();
1675 my $node = extract_param
($param, 'node');
1677 my $vmid = extract_param
($param, 'vmid');
1679 my $skiplock = extract_param
($param, 'skiplock');
1680 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1681 if $skiplock && $authuser ne 'root@pam';
1683 my $keepActive = extract_param
($param, 'keepActive');
1684 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1685 if $keepActive && $authuser ne 'root@pam';
1687 my $migratedfrom = extract_param
($param, 'migratedfrom');
1688 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1689 if $migratedfrom && $authuser ne 'root@pam';
1692 my $storecfg = PVE
::Storage
::config
();
1694 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1699 my $service = "vm:$vmid";
1701 my $cmd = ['ha-manager', 'disable', $service];
1703 print "Executing HA stop for VM $vmid\n";
1705 PVE
::Tools
::run_command
($cmd);
1710 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1716 syslog
('info', "stop VM $vmid: $upid\n");
1718 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1719 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1724 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1728 __PACKAGE__-
>register_method({
1730 path
=> '{vmid}/status/reset',
1734 description
=> "Reset virtual machine.",
1736 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1739 additionalProperties
=> 0,
1741 node
=> get_standard_option
('pve-node'),
1742 vmid
=> get_standard_option
('pve-vmid'),
1743 skiplock
=> get_standard_option
('skiplock'),
1752 my $rpcenv = PVE
::RPCEnvironment
::get
();
1754 my $authuser = $rpcenv->get_user();
1756 my $node = extract_param
($param, 'node');
1758 my $vmid = extract_param
($param, 'vmid');
1760 my $skiplock = extract_param
($param, 'skiplock');
1761 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1762 if $skiplock && $authuser ne 'root@pam';
1764 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1769 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1774 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1777 __PACKAGE__-
>register_method({
1778 name
=> 'vm_shutdown',
1779 path
=> '{vmid}/status/shutdown',
1783 description
=> "Shutdown virtual machine.",
1785 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1788 additionalProperties
=> 0,
1790 node
=> get_standard_option
('pve-node'),
1791 vmid
=> get_standard_option
('pve-vmid'),
1792 skiplock
=> get_standard_option
('skiplock'),
1794 description
=> "Wait maximal timeout seconds.",
1800 description
=> "Make sure the VM stops.",
1806 description
=> "Do not decativate storage volumes.",
1819 my $rpcenv = PVE
::RPCEnvironment
::get
();
1821 my $authuser = $rpcenv->get_user();
1823 my $node = extract_param
($param, 'node');
1825 my $vmid = extract_param
($param, 'vmid');
1827 my $skiplock = extract_param
($param, 'skiplock');
1828 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1829 if $skiplock && $authuser ne 'root@pam';
1831 my $keepActive = extract_param
($param, 'keepActive');
1832 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1833 if $keepActive && $authuser ne 'root@pam';
1835 my $storecfg = PVE
::Storage
::config
();
1840 syslog
('info', "shutdown VM $vmid: $upid\n");
1842 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1843 1, $param->{forceStop
}, $keepActive);
1848 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1851 __PACKAGE__-
>register_method({
1852 name
=> 'vm_suspend',
1853 path
=> '{vmid}/status/suspend',
1857 description
=> "Suspend virtual machine.",
1859 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1862 additionalProperties
=> 0,
1864 node
=> get_standard_option
('pve-node'),
1865 vmid
=> get_standard_option
('pve-vmid'),
1866 skiplock
=> get_standard_option
('skiplock'),
1875 my $rpcenv = PVE
::RPCEnvironment
::get
();
1877 my $authuser = $rpcenv->get_user();
1879 my $node = extract_param
($param, 'node');
1881 my $vmid = extract_param
($param, 'vmid');
1883 my $skiplock = extract_param
($param, 'skiplock');
1884 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1885 if $skiplock && $authuser ne 'root@pam';
1887 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1892 syslog
('info', "suspend VM $vmid: $upid\n");
1894 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1899 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1902 __PACKAGE__-
>register_method({
1903 name
=> 'vm_resume',
1904 path
=> '{vmid}/status/resume',
1908 description
=> "Resume virtual machine.",
1910 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1913 additionalProperties
=> 0,
1915 node
=> get_standard_option
('pve-node'),
1916 vmid
=> get_standard_option
('pve-vmid'),
1917 skiplock
=> get_standard_option
('skiplock'),
1926 my $rpcenv = PVE
::RPCEnvironment
::get
();
1928 my $authuser = $rpcenv->get_user();
1930 my $node = extract_param
($param, 'node');
1932 my $vmid = extract_param
($param, 'vmid');
1934 my $skiplock = extract_param
($param, 'skiplock');
1935 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1936 if $skiplock && $authuser ne 'root@pam';
1938 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1943 syslog
('info', "resume VM $vmid: $upid\n");
1945 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1950 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1953 __PACKAGE__-
>register_method({
1954 name
=> 'vm_sendkey',
1955 path
=> '{vmid}/sendkey',
1959 description
=> "Send key event to virtual machine.",
1961 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1964 additionalProperties
=> 0,
1966 node
=> get_standard_option
('pve-node'),
1967 vmid
=> get_standard_option
('pve-vmid'),
1968 skiplock
=> get_standard_option
('skiplock'),
1970 description
=> "The key (qemu monitor encoding).",
1975 returns
=> { type
=> 'null'},
1979 my $rpcenv = PVE
::RPCEnvironment
::get
();
1981 my $authuser = $rpcenv->get_user();
1983 my $node = extract_param
($param, 'node');
1985 my $vmid = extract_param
($param, 'vmid');
1987 my $skiplock = extract_param
($param, 'skiplock');
1988 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1989 if $skiplock && $authuser ne 'root@pam';
1991 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1996 __PACKAGE__-
>register_method({
1997 name
=> 'vm_feature',
1998 path
=> '{vmid}/feature',
2002 description
=> "Check if feature for virtual machine is available.",
2004 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2007 additionalProperties
=> 0,
2009 node
=> get_standard_option
('pve-node'),
2010 vmid
=> get_standard_option
('pve-vmid'),
2012 description
=> "Feature to check.",
2014 enum
=> [ 'snapshot', 'clone', 'copy' ],
2016 snapname
=> get_standard_option
('pve-snapshot-name', {
2024 hasFeature
=> { type
=> 'boolean' },
2027 items
=> { type
=> 'string' },
2034 my $node = extract_param
($param, 'node');
2036 my $vmid = extract_param
($param, 'vmid');
2038 my $snapname = extract_param
($param, 'snapname');
2040 my $feature = extract_param
($param, 'feature');
2042 my $running = PVE
::QemuServer
::check_running
($vmid);
2044 my $conf = PVE
::QemuServer
::load_config
($vmid);
2047 my $snap = $conf->{snapshots
}->{$snapname};
2048 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2051 my $storecfg = PVE
::Storage
::config
();
2053 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2054 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2057 hasFeature
=> $hasFeature,
2058 nodes
=> [ keys %$nodelist ],
2062 __PACKAGE__-
>register_method({
2064 path
=> '{vmid}/clone',
2068 description
=> "Create a copy of virtual machine/template.",
2070 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2071 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2072 "'Datastore.AllocateSpace' on any used storage.",
2075 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2077 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2078 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2083 additionalProperties
=> 0,
2085 node
=> get_standard_option
('pve-node'),
2086 vmid
=> get_standard_option
('pve-vmid'),
2087 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2090 type
=> 'string', format
=> 'dns-name',
2091 description
=> "Set a name for the new VM.",
2096 description
=> "Description for the new VM.",
2100 type
=> 'string', format
=> 'pve-poolid',
2101 description
=> "Add the new VM to the specified pool.",
2103 snapname
=> get_standard_option
('pve-snapshot-name', {
2106 storage
=> get_standard_option
('pve-storage-id', {
2107 description
=> "Target storage for full clone.",
2112 description
=> "Target format for file storage.",
2116 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2121 description
=> "Create a full copy of all disk. This is always done when " .
2122 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2125 target
=> get_standard_option
('pve-node', {
2126 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2137 my $rpcenv = PVE
::RPCEnvironment
::get
();
2139 my $authuser = $rpcenv->get_user();
2141 my $node = extract_param
($param, 'node');
2143 my $vmid = extract_param
($param, 'vmid');
2145 my $newid = extract_param
($param, 'newid');
2147 my $pool = extract_param
($param, 'pool');
2149 if (defined($pool)) {
2150 $rpcenv->check_pool_exist($pool);
2153 my $snapname = extract_param
($param, 'snapname');
2155 my $storage = extract_param
($param, 'storage');
2157 my $format = extract_param
($param, 'format');
2159 my $target = extract_param
($param, 'target');
2161 my $localnode = PVE
::INotify
::nodename
();
2163 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2165 PVE
::Cluster
::check_node_exists
($target) if $target;
2167 my $storecfg = PVE
::Storage
::config
();
2170 # check if storage is enabled on local node
2171 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2173 # check if storage is available on target node
2174 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2175 # clone only works if target storage is shared
2176 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2177 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2181 PVE
::Cluster
::check_cfs_quorum
();
2183 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2185 # exclusive lock if VM is running - else shared lock is enough;
2186 my $shared_lock = $running ?
0 : 1;
2190 # do all tests after lock
2191 # we also try to do all tests before we fork the worker
2193 my $conf = PVE
::QemuServer
::load_config
($vmid);
2195 PVE
::QemuServer
::check_lock
($conf);
2197 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2199 die "unexpected state change\n" if $verify_running != $running;
2201 die "snapshot '$snapname' does not exist\n"
2202 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2204 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2206 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2208 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2210 my $conffile = PVE
::QemuServer
::config_file
($newid);
2212 die "unable to create VM $newid: config file already exists\n"
2215 my $newconf = { lock => 'clone' };
2219 foreach my $opt (keys %$oldconf) {
2220 my $value = $oldconf->{$opt};
2222 # do not copy snapshot related info
2223 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2224 $opt eq 'vmstate' || $opt eq 'snapstate';
2226 # always change MAC! address
2227 if ($opt =~ m/^net(\d+)$/) {
2228 my $net = PVE
::QemuServer
::parse_net
($value);
2229 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2230 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2231 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2232 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2233 die "unable to parse drive options for '$opt'\n" if !$drive;
2234 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2235 $newconf->{$opt} = $value; # simply copy configuration
2237 if ($param->{full
}) {
2238 die "Full clone feature is not available"
2239 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2242 # not full means clone instead of copy
2243 die "Linked clone feature is not available"
2244 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2246 $drives->{$opt} = $drive;
2247 push @$vollist, $drive->{file
};
2250 # copy everything else
2251 $newconf->{$opt} = $value;
2255 # auto generate a new uuid
2256 my ($uuid, $uuid_str);
2257 UUID
::generate
($uuid);
2258 UUID
::unparse
($uuid, $uuid_str);
2259 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2260 $smbios1->{uuid
} = $uuid_str;
2261 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2263 delete $newconf->{template
};
2265 if ($param->{name
}) {
2266 $newconf->{name
} = $param->{name
};
2268 if ($oldconf->{name
}) {
2269 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2271 $newconf->{name
} = "Copy-of-VM-$vmid";
2275 if ($param->{description
}) {
2276 $newconf->{description
} = $param->{description
};
2279 # create empty/temp config - this fails if VM already exists on other node
2280 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2285 my $newvollist = [];
2288 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2290 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2292 foreach my $opt (keys %$drives) {
2293 my $drive = $drives->{$opt};
2295 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2296 $newid, $storage, $format, $drive->{full
}, $newvollist);
2298 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2300 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2303 delete $newconf->{lock};
2304 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2307 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2308 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2310 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2311 die "Failed to move config to node '$target' - rename failed: $!\n"
2312 if !rename($conffile, $newconffile);
2315 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2320 sleep 1; # some storage like rbd need to wait before release volume - really?
2322 foreach my $volid (@$newvollist) {
2323 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2326 die "clone failed: $err";
2332 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2335 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2336 # Aquire exclusive lock lock for $newid
2337 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2342 __PACKAGE__-
>register_method({
2343 name
=> 'move_vm_disk',
2344 path
=> '{vmid}/move_disk',
2348 description
=> "Move volume to different storage.",
2350 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2351 "and 'Datastore.AllocateSpace' permissions on the storage.",
2354 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2355 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2359 additionalProperties
=> 0,
2361 node
=> get_standard_option
('pve-node'),
2362 vmid
=> get_standard_option
('pve-vmid'),
2365 description
=> "The disk you want to move.",
2366 enum
=> [ PVE
::QemuServer
::disknames
() ],
2368 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2371 description
=> "Target Format.",
2372 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2377 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2383 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2391 description
=> "the task ID.",
2396 my $rpcenv = PVE
::RPCEnvironment
::get
();
2398 my $authuser = $rpcenv->get_user();
2400 my $node = extract_param
($param, 'node');
2402 my $vmid = extract_param
($param, 'vmid');
2404 my $digest = extract_param
($param, 'digest');
2406 my $disk = extract_param
($param, 'disk');
2408 my $storeid = extract_param
($param, 'storage');
2410 my $format = extract_param
($param, 'format');
2412 my $storecfg = PVE
::Storage
::config
();
2414 my $updatefn = sub {
2416 my $conf = PVE
::QemuServer
::load_config
($vmid);
2418 die "checksum missmatch (file change by other user?)\n"
2419 if $digest && $digest ne $conf->{digest
};
2421 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2423 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2425 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2427 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2430 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2431 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2435 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2436 (!$format || !$oldfmt || $oldfmt eq $format);
2438 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2440 my $running = PVE
::QemuServer
::check_running
($vmid);
2442 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2446 my $newvollist = [];
2449 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2451 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2452 $vmid, $storeid, $format, 1, $newvollist);
2454 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2456 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2458 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2461 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2462 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2469 foreach my $volid (@$newvollist) {
2470 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2473 die "storage migration failed: $err";
2476 if ($param->{delete}) {
2477 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2478 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2479 if ($used_paths->{$path}){
2480 warn "volume $old_volid have snapshots. Can't delete it\n";
2481 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2482 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2484 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2490 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2493 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2496 __PACKAGE__-
>register_method({
2497 name
=> 'migrate_vm',
2498 path
=> '{vmid}/migrate',
2502 description
=> "Migrate virtual machine. Creates a new migration task.",
2504 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2507 additionalProperties
=> 0,
2509 node
=> get_standard_option
('pve-node'),
2510 vmid
=> get_standard_option
('pve-vmid'),
2511 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2514 description
=> "Use online/live migration.",
2519 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2526 description
=> "the task ID.",
2531 my $rpcenv = PVE
::RPCEnvironment
::get
();
2533 my $authuser = $rpcenv->get_user();
2535 my $target = extract_param
($param, 'target');
2537 my $localnode = PVE
::INotify
::nodename
();
2538 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2540 PVE
::Cluster
::check_cfs_quorum
();
2542 PVE
::Cluster
::check_node_exists
($target);
2544 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2546 my $vmid = extract_param
($param, 'vmid');
2548 raise_param_exc
({ force
=> "Only root may use this option." })
2549 if $param->{force
} && $authuser ne 'root@pam';
2552 my $conf = PVE
::QemuServer
::load_config
($vmid);
2554 # try to detect errors early
2556 PVE
::QemuServer
::check_lock
($conf);
2558 if (PVE
::QemuServer
::check_running
($vmid)) {
2559 die "cant migrate running VM without --online\n"
2560 if !$param->{online
};
2563 my $storecfg = PVE
::Storage
::config
();
2564 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2566 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2571 my $service = "vm:$vmid";
2573 my $cmd = ['ha-manager', 'migrate', $service, $target];
2575 print "Executing HA migrate for VM $vmid to node $target\n";
2577 PVE
::Tools
::run_command
($cmd);
2582 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2589 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2592 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2597 __PACKAGE__-
>register_method({
2599 path
=> '{vmid}/monitor',
2603 description
=> "Execute Qemu monitor commands.",
2605 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2608 additionalProperties
=> 0,
2610 node
=> get_standard_option
('pve-node'),
2611 vmid
=> get_standard_option
('pve-vmid'),
2614 description
=> "The monitor command.",
2618 returns
=> { type
=> 'string'},
2622 my $vmid = $param->{vmid
};
2624 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2628 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2630 $res = "ERROR: $@" if $@;
2635 __PACKAGE__-
>register_method({
2636 name
=> 'resize_vm',
2637 path
=> '{vmid}/resize',
2641 description
=> "Extend volume size.",
2643 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2646 additionalProperties
=> 0,
2648 node
=> get_standard_option
('pve-node'),
2649 vmid
=> get_standard_option
('pve-vmid'),
2650 skiplock
=> get_standard_option
('skiplock'),
2653 description
=> "The disk you want to resize.",
2654 enum
=> [PVE
::QemuServer
::disknames
()],
2658 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2659 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.",
2663 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2669 returns
=> { type
=> 'null'},
2673 my $rpcenv = PVE
::RPCEnvironment
::get
();
2675 my $authuser = $rpcenv->get_user();
2677 my $node = extract_param
($param, 'node');
2679 my $vmid = extract_param
($param, 'vmid');
2681 my $digest = extract_param
($param, 'digest');
2683 my $disk = extract_param
($param, 'disk');
2685 my $sizestr = extract_param
($param, 'size');
2687 my $skiplock = extract_param
($param, 'skiplock');
2688 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2689 if $skiplock && $authuser ne 'root@pam';
2691 my $storecfg = PVE
::Storage
::config
();
2693 my $updatefn = sub {
2695 my $conf = PVE
::QemuServer
::load_config
($vmid);
2697 die "checksum missmatch (file change by other user?)\n"
2698 if $digest && $digest ne $conf->{digest
};
2699 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2701 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2703 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2705 my $volid = $drive->{file
};
2707 die "disk '$disk' has no associated volume\n" if !$volid;
2709 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2711 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2713 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2715 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2717 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2718 my ($ext, $newsize, $unit) = ($1, $2, $4);
2721 $newsize = $newsize * 1024;
2722 } elsif ($unit eq 'M') {
2723 $newsize = $newsize * 1024 * 1024;
2724 } elsif ($unit eq 'G') {
2725 $newsize = $newsize * 1024 * 1024 * 1024;
2726 } elsif ($unit eq 'T') {
2727 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2730 $newsize += $size if $ext;
2731 $newsize = int($newsize);
2733 die "unable to skrink disk size\n" if $newsize < $size;
2735 return if $size == $newsize;
2737 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2739 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2741 $drive->{size
} = $newsize;
2742 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2744 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2747 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2751 __PACKAGE__-
>register_method({
2752 name
=> 'snapshot_list',
2753 path
=> '{vmid}/snapshot',
2755 description
=> "List all snapshots.",
2757 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2760 protected
=> 1, # qemu pid files are only readable by root
2762 additionalProperties
=> 0,
2764 vmid
=> get_standard_option
('pve-vmid'),
2765 node
=> get_standard_option
('pve-node'),
2774 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2779 my $vmid = $param->{vmid
};
2781 my $conf = PVE
::QemuServer
::load_config
($vmid);
2782 my $snaphash = $conf->{snapshots
} || {};
2786 foreach my $name (keys %$snaphash) {
2787 my $d = $snaphash->{$name};
2790 snaptime
=> $d->{snaptime
} || 0,
2791 vmstate
=> $d->{vmstate
} ?
1 : 0,
2792 description
=> $d->{description
} || '',
2794 $item->{parent
} = $d->{parent
} if $d->{parent
};
2795 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2799 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2800 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2801 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2803 push @$res, $current;
2808 __PACKAGE__-
>register_method({
2810 path
=> '{vmid}/snapshot',
2814 description
=> "Snapshot a VM.",
2816 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2819 additionalProperties
=> 0,
2821 node
=> get_standard_option
('pve-node'),
2822 vmid
=> get_standard_option
('pve-vmid'),
2823 snapname
=> get_standard_option
('pve-snapshot-name'),
2827 description
=> "Save the vmstate",
2832 description
=> "A textual description or comment.",
2838 description
=> "the task ID.",
2843 my $rpcenv = PVE
::RPCEnvironment
::get
();
2845 my $authuser = $rpcenv->get_user();
2847 my $node = extract_param
($param, 'node');
2849 my $vmid = extract_param
($param, 'vmid');
2851 my $snapname = extract_param
($param, 'snapname');
2853 die "unable to use snapshot name 'current' (reserved name)\n"
2854 if $snapname eq 'current';
2857 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2858 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2859 $param->{description
});
2862 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2865 __PACKAGE__-
>register_method({
2866 name
=> 'snapshot_cmd_idx',
2867 path
=> '{vmid}/snapshot/{snapname}',
2874 additionalProperties
=> 0,
2876 vmid
=> get_standard_option
('pve-vmid'),
2877 node
=> get_standard_option
('pve-node'),
2878 snapname
=> get_standard_option
('pve-snapshot-name'),
2887 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2894 push @$res, { cmd
=> 'rollback' };
2895 push @$res, { cmd
=> 'config' };
2900 __PACKAGE__-
>register_method({
2901 name
=> 'update_snapshot_config',
2902 path
=> '{vmid}/snapshot/{snapname}/config',
2906 description
=> "Update snapshot metadata.",
2908 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2911 additionalProperties
=> 0,
2913 node
=> get_standard_option
('pve-node'),
2914 vmid
=> get_standard_option
('pve-vmid'),
2915 snapname
=> get_standard_option
('pve-snapshot-name'),
2919 description
=> "A textual description or comment.",
2923 returns
=> { type
=> 'null' },
2927 my $rpcenv = PVE
::RPCEnvironment
::get
();
2929 my $authuser = $rpcenv->get_user();
2931 my $vmid = extract_param
($param, 'vmid');
2933 my $snapname = extract_param
($param, 'snapname');
2935 return undef if !defined($param->{description
});
2937 my $updatefn = sub {
2939 my $conf = PVE
::QemuServer
::load_config
($vmid);
2941 PVE
::QemuServer
::check_lock
($conf);
2943 my $snap = $conf->{snapshots
}->{$snapname};
2945 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2947 $snap->{description
} = $param->{description
} if defined($param->{description
});
2949 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2952 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2957 __PACKAGE__-
>register_method({
2958 name
=> 'get_snapshot_config',
2959 path
=> '{vmid}/snapshot/{snapname}/config',
2962 description
=> "Get snapshot configuration",
2964 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2967 additionalProperties
=> 0,
2969 node
=> get_standard_option
('pve-node'),
2970 vmid
=> get_standard_option
('pve-vmid'),
2971 snapname
=> get_standard_option
('pve-snapshot-name'),
2974 returns
=> { type
=> "object" },
2978 my $rpcenv = PVE
::RPCEnvironment
::get
();
2980 my $authuser = $rpcenv->get_user();
2982 my $vmid = extract_param
($param, 'vmid');
2984 my $snapname = extract_param
($param, 'snapname');
2986 my $conf = PVE
::QemuServer
::load_config
($vmid);
2988 my $snap = $conf->{snapshots
}->{$snapname};
2990 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2995 __PACKAGE__-
>register_method({
2997 path
=> '{vmid}/snapshot/{snapname}/rollback',
3001 description
=> "Rollback VM state to specified snapshot.",
3003 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3006 additionalProperties
=> 0,
3008 node
=> get_standard_option
('pve-node'),
3009 vmid
=> get_standard_option
('pve-vmid'),
3010 snapname
=> get_standard_option
('pve-snapshot-name'),
3015 description
=> "the task ID.",
3020 my $rpcenv = PVE
::RPCEnvironment
::get
();
3022 my $authuser = $rpcenv->get_user();
3024 my $node = extract_param
($param, 'node');
3026 my $vmid = extract_param
($param, 'vmid');
3028 my $snapname = extract_param
($param, 'snapname');
3031 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3032 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3035 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3038 __PACKAGE__-
>register_method({
3039 name
=> 'delsnapshot',
3040 path
=> '{vmid}/snapshot/{snapname}',
3044 description
=> "Delete a VM snapshot.",
3046 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3049 additionalProperties
=> 0,
3051 node
=> get_standard_option
('pve-node'),
3052 vmid
=> get_standard_option
('pve-vmid'),
3053 snapname
=> get_standard_option
('pve-snapshot-name'),
3057 description
=> "For removal from config file, even if removing disk snapshots fails.",
3063 description
=> "the task ID.",
3068 my $rpcenv = PVE
::RPCEnvironment
::get
();
3070 my $authuser = $rpcenv->get_user();
3072 my $node = extract_param
($param, 'node');
3074 my $vmid = extract_param
($param, 'vmid');
3076 my $snapname = extract_param
($param, 'snapname');
3079 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3080 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3083 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3086 __PACKAGE__-
>register_method({
3088 path
=> '{vmid}/template',
3092 description
=> "Create a Template.",
3094 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3095 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3098 additionalProperties
=> 0,
3100 node
=> get_standard_option
('pve-node'),
3101 vmid
=> get_standard_option
('pve-vmid'),
3105 description
=> "If you want to convert only 1 disk to base image.",
3106 enum
=> [PVE
::QemuServer
::disknames
()],
3111 returns
=> { type
=> 'null'},
3115 my $rpcenv = PVE
::RPCEnvironment
::get
();
3117 my $authuser = $rpcenv->get_user();
3119 my $node = extract_param
($param, 'node');
3121 my $vmid = extract_param
($param, 'vmid');
3123 my $disk = extract_param
($param, 'disk');
3125 my $updatefn = sub {
3127 my $conf = PVE
::QemuServer
::load_config
($vmid);
3129 PVE
::QemuServer
::check_lock
($conf);
3131 die "unable to create template, because VM contains snapshots\n"
3132 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3134 die "you can't convert a template to a template\n"
3135 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3137 die "you can't convert a VM to template if VM is running\n"
3138 if PVE
::QemuServer
::check_running
($vmid);
3141 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3144 $conf->{template
} = 1;
3145 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3147 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3150 PVE
::QemuServer
::lock_config
($vmid, $updatefn);