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
;
24 use Data
::Dumper
; # fixme: remove
26 use base
qw(PVE::RESTHandler);
28 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.";
30 my $resolve_cdrom_alias = sub {
33 if (my $value = $param->{cdrom
}) {
34 $value .= ",media=cdrom" if $value !~ m/media=/;
35 $param->{ide2
} = $value;
36 delete $param->{cdrom
};
40 my $test_deallocate_drive = sub {
41 my ($storecfg, $vmid, $key, $drive, $force) = @_;
43 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
44 my $volid = $drive->{file
};
45 if ( PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
46 if ($force || $key =~ m/^unused/) {
47 my $sid = PVE
::Storage
::parse_volume_id
($volid);
56 my $check_storage_access = sub {
57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
59 PVE
::QemuServer
::foreach_drive
($settings, sub {
60 my ($ds, $drive) = @_;
62 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
64 my $volid = $drive->{file
};
66 if (!$volid || $volid eq 'none') {
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
70 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
75 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
80 my $check_storage_access_clone = sub {
81 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
85 PVE
::QemuServer
::foreach_drive
($conf, sub {
86 my ($ds, $drive) = @_;
88 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
90 my $volid = $drive->{file
};
92 return if !$volid || $volid eq 'none';
95 if ($volid eq 'cdrom') {
96 $rpcenv->check($authuser, "/", ['Sys.Console']);
98 # we simply allow access
99 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
100 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
101 $sharedvm = 0 if !$scfg->{shared
};
105 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
106 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
107 $sharedvm = 0 if !$scfg->{shared
};
109 $sid = $storage if $storage;
110 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
117 # Note: $pool is only needed when creating a VM, because pool permissions
118 # are automatically inherited if VM already exists inside a pool.
119 my $create_disks = sub {
120 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
125 PVE
::QemuServer
::foreach_drive
($settings, sub {
126 my ($ds, $disk) = @_;
128 my $volid = $disk->{file
};
130 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
131 delete $disk->{size
};
132 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
133 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
134 my ($storeid, $size) = ($2 || $default_storage, $3);
135 die "no storage ID specified (and no default storage)\n" if !$storeid;
136 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
137 my $fmt = $disk->{format
} || $defformat;
138 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
139 $fmt, undef, $size*1024*1024);
140 $disk->{file
} = $volid;
141 $disk->{size
} = $size*1024*1024*1024;
142 push @$vollist, $volid;
143 delete $disk->{format
}; # no longer needed
144 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
149 my $volid_is_new = 1;
152 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
153 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
158 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
160 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
162 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
164 die "volume $volid does not exists\n" if !$size;
166 $disk->{size
} = $size;
169 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
173 # free allocated images on error
175 syslog
('err', "VM $vmid creating disks failed");
176 foreach my $volid (@$vollist) {
177 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
183 # modify vm config if everything went well
184 foreach my $ds (keys %$res) {
185 $conf->{$ds} = $res->{$ds};
191 my $delete_drive = sub {
192 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
194 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
195 my $volid = $drive->{file
};
197 if (PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
198 if ($force || $key =~ m/^unused/) {
200 # check if the disk is really unused
201 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
202 my $path = PVE
::Storage
::path
($storecfg, $volid);
204 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
205 if $used_paths->{$path};
207 PVE
::Storage
::vdisk_free
($storecfg, $volid);
211 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
216 delete $conf->{$key};
219 my $check_vm_modify_config_perm = sub {
220 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
222 return 1 if $authuser eq 'root@pam';
224 foreach my $opt (@$key_list) {
225 # disk checks need to be done somewhere else
226 next if PVE
::QemuServer
::valid_drivename
($opt);
228 if ($opt eq 'sockets' || $opt eq 'cores' ||
229 $opt eq 'cpu' || $opt eq 'smp' ||
230 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
231 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
232 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
233 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
234 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
235 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
236 } elsif ($opt eq 'args' || $opt eq 'lock') {
237 die "only root can set '$opt' config\n";
238 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
239 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
240 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
241 } elsif ($opt =~ m/^net\d+$/) {
242 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
244 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
251 __PACKAGE__-
>register_method({
255 description
=> "Virtual machine index (per node).",
257 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
261 protected
=> 1, # qemu pid files are only readable by root
263 additionalProperties
=> 0,
265 node
=> get_standard_option
('pve-node'),
274 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
279 my $rpcenv = PVE
::RPCEnvironment
::get
();
280 my $authuser = $rpcenv->get_user();
282 my $vmstatus = PVE
::QemuServer
::vmstatus
();
285 foreach my $vmid (keys %$vmstatus) {
286 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
288 my $data = $vmstatus->{$vmid};
289 $data->{vmid
} = int($vmid);
298 __PACKAGE__-
>register_method({
302 description
=> "Create or restore a virtual machine.",
304 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
305 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
306 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
307 user
=> 'all', # check inside
312 additionalProperties
=> 0,
313 properties
=> PVE
::QemuServer
::json_config_properties
(
315 node
=> get_standard_option
('pve-node'),
316 vmid
=> get_standard_option
('pve-vmid'),
318 description
=> "The backup file.",
323 storage
=> get_standard_option
('pve-storage-id', {
324 description
=> "Default storage.",
330 description
=> "Allow to overwrite existing VM.",
331 requires
=> 'archive',
336 description
=> "Assign a unique random ethernet address.",
337 requires
=> 'archive',
341 type
=> 'string', format
=> 'pve-poolid',
342 description
=> "Add the VM to the specified pool.",
352 my $rpcenv = PVE
::RPCEnvironment
::get
();
354 my $authuser = $rpcenv->get_user();
356 my $node = extract_param
($param, 'node');
358 my $vmid = extract_param
($param, 'vmid');
360 my $archive = extract_param
($param, 'archive');
362 my $storage = extract_param
($param, 'storage');
364 my $force = extract_param
($param, 'force');
366 my $unique = extract_param
($param, 'unique');
368 my $pool = extract_param
($param, 'pool');
370 my $filename = PVE
::QemuServer
::config_file
($vmid);
372 my $storecfg = PVE
::Storage
::config
();
374 PVE
::Cluster
::check_cfs_quorum
();
376 if (defined($pool)) {
377 $rpcenv->check_pool_exist($pool);
380 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
381 if defined($storage);
383 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
385 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
387 } elsif ($archive && $force && (-f
$filename) &&
388 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
389 # OK: user has VM.Backup permissions, and want to restore an existing VM
395 &$resolve_cdrom_alias($param);
397 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
399 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
401 foreach my $opt (keys %$param) {
402 if (PVE
::QemuServer
::valid_drivename
($opt)) {
403 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
404 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
406 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
407 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
411 PVE
::QemuServer
::add_random_macs
($param);
413 my $keystr = join(' ', keys %$param);
414 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
416 if ($archive eq '-') {
417 die "pipe requires cli environment\n"
418 if $rpcenv->{type
} ne 'cli';
420 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
421 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
425 my $restorefn = sub {
427 # fixme: this test does not work if VM exists on other node!
429 die "unable to restore vm $vmid: config file already exists\n"
432 die "unable to restore vm $vmid: vm is running\n"
433 if PVE
::QemuServer
::check_running
($vmid);
437 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
440 unique
=> $unique });
442 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
445 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
451 die "unable to create vm $vmid: config file already exists\n"
462 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
464 # try to be smart about bootdisk
465 my @disks = PVE
::QemuServer
::disknames
();
467 foreach my $ds (reverse @disks) {
468 next if !$conf->{$ds};
469 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
470 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
474 if (!$conf->{bootdisk
} && $firstdisk) {
475 $conf->{bootdisk
} = $firstdisk;
478 # auto generate uuid if user did not specify smbios1 option
479 if (!$conf->{smbios1
}) {
480 my ($uuid, $uuid_str);
481 UUID
::generate
($uuid);
482 UUID
::unparse
($uuid, $uuid_str);
483 $conf->{smbios1
} = "uuid=$uuid_str";
486 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
492 foreach my $volid (@$vollist) {
493 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
496 die "create failed - $err";
499 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
502 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
505 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
508 __PACKAGE__-
>register_method({
513 description
=> "Directory index",
518 additionalProperties
=> 0,
520 node
=> get_standard_option
('pve-node'),
521 vmid
=> get_standard_option
('pve-vmid'),
529 subdir
=> { type
=> 'string' },
532 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
538 { subdir
=> 'config' },
539 { subdir
=> 'pending' },
540 { subdir
=> 'status' },
541 { subdir
=> 'unlink' },
542 { subdir
=> 'vncproxy' },
543 { subdir
=> 'migrate' },
544 { subdir
=> 'resize' },
545 { subdir
=> 'move' },
547 { subdir
=> 'rrddata' },
548 { subdir
=> 'monitor' },
549 { subdir
=> 'snapshot' },
550 { subdir
=> 'spiceproxy' },
551 { subdir
=> 'sendkey' },
552 { subdir
=> 'firewall' },
558 __PACKAGE__-
>register_method ({
559 subclass
=> "PVE::API2::Firewall::VM",
560 path
=> '{vmid}/firewall',
563 __PACKAGE__-
>register_method({
565 path
=> '{vmid}/rrd',
567 protected
=> 1, # fixme: can we avoid that?
569 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
571 description
=> "Read VM RRD statistics (returns PNG)",
573 additionalProperties
=> 0,
575 node
=> get_standard_option
('pve-node'),
576 vmid
=> get_standard_option
('pve-vmid'),
578 description
=> "Specify the time frame you are interested in.",
580 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
583 description
=> "The list of datasources you want to display.",
584 type
=> 'string', format
=> 'pve-configid-list',
587 description
=> "The RRD consolidation function",
589 enum
=> [ 'AVERAGE', 'MAX' ],
597 filename
=> { type
=> 'string' },
603 return PVE
::Cluster
::create_rrd_graph
(
604 "pve2-vm/$param->{vmid}", $param->{timeframe
},
605 $param->{ds
}, $param->{cf
});
609 __PACKAGE__-
>register_method({
611 path
=> '{vmid}/rrddata',
613 protected
=> 1, # fixme: can we avoid that?
615 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
617 description
=> "Read VM RRD statistics",
619 additionalProperties
=> 0,
621 node
=> get_standard_option
('pve-node'),
622 vmid
=> get_standard_option
('pve-vmid'),
624 description
=> "Specify the time frame you are interested in.",
626 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
629 description
=> "The RRD consolidation function",
631 enum
=> [ 'AVERAGE', 'MAX' ],
646 return PVE
::Cluster
::create_rrd_data
(
647 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
651 __PACKAGE__-
>register_method({
653 path
=> '{vmid}/config',
656 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
658 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
661 additionalProperties
=> 0,
663 node
=> get_standard_option
('pve-node'),
664 vmid
=> get_standard_option
('pve-vmid'),
666 description
=> "Get current values (instead of pending values).",
678 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
685 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
687 delete $conf->{snapshots
};
689 if (!$param->{current
}) {
690 foreach my $opt (keys $conf->{pending
}) {
691 next if $opt eq 'delete';
692 my $value = $conf->{pending
}->{$opt};
693 next if ref($value); # just to be sure
694 $conf->{$opt} = $value;
696 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
697 delete $conf->{$opt} if $conf->{$opt};
701 delete $conf->{pending
};
706 __PACKAGE__-
>register_method({
707 name
=> 'vm_pending',
708 path
=> '{vmid}/pending',
711 description
=> "Get virtual machine configuration, including pending changes.",
713 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
716 additionalProperties
=> 0,
718 node
=> get_standard_option
('pve-node'),
719 vmid
=> get_standard_option
('pve-vmid'),
728 description
=> "Configuration option name.",
732 description
=> "Current value.",
737 description
=> "Pending value.",
742 description
=> "Indicated a pending delete request.",
752 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
754 my $pending_delete_hash = {};
755 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
756 $pending_delete_hash->{$opt} = 1;
761 foreach my $opt (keys $conf) {
762 next if ref($conf->{$opt});
763 my $item = { key
=> $opt };
764 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
765 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
766 $item->{delete} = 1 if $pending_delete_hash->{$opt};
770 foreach my $opt (keys $conf->{pending
}) {
771 next if $opt eq 'delete';
772 next if ref($conf->{pending
}->{$opt}); # just to be sure
773 next if defined($conf->{$opt});
774 my $item = { key
=> $opt };
775 $item->{pending
} = $conf->{pending
}->{$opt};
779 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
780 next if $conf->{pending
}->{$opt}; # just to be sure
781 next if $conf->{$opt};
782 my $item = { key
=> $opt, delete => 1};
789 # POST/PUT {vmid}/config implementation
791 # The original API used PUT (idempotent) an we assumed that all operations
792 # are fast. But it turned out that almost any configuration change can
793 # involve hot-plug actions, or disk alloc/free. Such actions can take long
794 # time to complete and have side effects (not idempotent).
796 # The new implementation uses POST and forks a worker process. We added
797 # a new option 'background_delay'. If specified we wait up to
798 # 'background_delay' second for the worker task to complete. It returns null
799 # if the task is finished within that time, else we return the UPID.
801 my $update_vm_api = sub {
802 my ($param, $sync) = @_;
804 my $rpcenv = PVE
::RPCEnvironment
::get
();
806 my $authuser = $rpcenv->get_user();
808 my $node = extract_param
($param, 'node');
810 my $vmid = extract_param
($param, 'vmid');
812 my $digest = extract_param
($param, 'digest');
814 my $background_delay = extract_param
($param, 'background_delay');
816 my @paramarr = (); # used for log message
817 foreach my $key (keys %$param) {
818 push @paramarr, "-$key", $param->{$key};
821 my $skiplock = extract_param
($param, 'skiplock');
822 raise_param_exc
({ skiplock
=> "Only root may use this option." })
823 if $skiplock && $authuser ne 'root@pam';
825 my $delete_str = extract_param
($param, 'delete');
827 my $revert_str = extract_param
($param, 'revert');
829 my $force = extract_param
($param, 'force');
831 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
833 my $storecfg = PVE
::Storage
::config
();
835 my $defaults = PVE
::QemuServer
::load_defaults
();
837 &$resolve_cdrom_alias($param);
839 # now try to verify all parameters
842 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
843 if (!PVE
::QemuServer
::option_exists
($opt)) {
844 raise_param_exc
({ revert
=> "unknown option '$opt'" });
847 raise_param_exc
({ delete => "you can't use '-$opt' and " .
848 "-revert $opt' at the same time" })
849 if defined($param->{$opt});
855 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
856 $opt = 'ide2' if $opt eq 'cdrom';
858 raise_param_exc
({ delete => "you can't use '-$opt' and " .
859 "-delete $opt' at the same time" })
860 if defined($param->{$opt});
862 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
863 "-revert $opt' at the same time" })
866 if (!PVE
::QemuServer
::option_exists
($opt)) {
867 raise_param_exc
({ delete => "unknown option '$opt'" });
873 foreach my $opt (keys %$param) {
874 if (PVE
::QemuServer
::valid_drivename
($opt)) {
876 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
877 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
878 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
879 } elsif ($opt =~ m/^net(\d+)$/) {
881 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
882 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
886 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
888 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
890 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
894 my $conf = PVE
::QemuServer
::load_config
($vmid);
896 die "checksum missmatch (file change by other user?)\n"
897 if $digest && $digest ne $conf->{digest
};
899 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
901 foreach my $opt (keys %$revert) {
902 if (defined($conf->{$opt})) {
903 $param->{$opt} = $conf->{$opt};
904 } elsif (defined($conf->{pending
}->{$opt})) {
909 if ($param->{memory
} || defined($param->{balloon
})) {
910 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
911 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
913 die "balloon value too large (must be smaller than assigned memory)\n"
914 if $balloon && $balloon > $maxmem;
917 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
921 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
923 # write updates to pending section
925 my $modified = {}; # record what $option we modify
927 foreach my $opt (@delete) {
928 $modified->{$opt} = 1;
929 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
930 if ($opt =~ m/^unused/) {
931 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
932 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
933 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
934 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
935 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
936 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
938 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
939 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
940 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
941 if defined($conf->{pending
}->{$opt});
942 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
943 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
945 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
946 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
950 foreach my $opt (keys %$param) { # add/change
951 $modified->{$opt} = 1;
952 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
953 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
955 if (PVE
::QemuServer
::valid_drivename
($opt)) {
956 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
957 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
958 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
960 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
962 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
963 if defined($conf->{pending
}->{$opt});
965 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
967 $conf->{pending
}->{$opt} = $param->{$opt};
969 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
970 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
973 # remove pending changes when nothing changed
974 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
975 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
976 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
978 return if !scalar(keys %{$conf->{pending
}});
980 my $running = PVE
::QemuServer
::check_running
($vmid);
982 # apply pending changes
984 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
988 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
989 raise_param_exc
($errors) if scalar(keys %$errors);
991 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1001 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1003 if ($background_delay) {
1005 # Note: It would be better to do that in the Event based HTTPServer
1006 # to avoid blocking call to sleep.
1008 my $end_time = time() + $background_delay;
1010 my $task = PVE
::Tools
::upid_decode
($upid);
1013 while (time() < $end_time) {
1014 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1016 sleep(1); # this gets interrupted when child process ends
1020 my $status = PVE
::Tools
::upid_read_status
($upid);
1021 return undef if $status eq 'OK';
1030 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1033 my $vm_config_perm_list = [
1038 'VM.Config.Network',
1040 'VM.Config.Options',
1043 __PACKAGE__-
>register_method({
1044 name
=> 'update_vm_async',
1045 path
=> '{vmid}/config',
1049 description
=> "Set virtual machine options (asynchrounous API).",
1051 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1054 additionalProperties
=> 0,
1055 properties
=> PVE
::QemuServer
::json_config_properties
(
1057 node
=> get_standard_option
('pve-node'),
1058 vmid
=> get_standard_option
('pve-vmid'),
1059 skiplock
=> get_standard_option
('skiplock'),
1061 type
=> 'string', format
=> 'pve-configid-list',
1062 description
=> "A list of settings you want to delete.",
1066 type
=> 'string', format
=> 'pve-configid-list',
1067 description
=> "Revert a pending change.",
1072 description
=> $opt_force_description,
1074 requires
=> 'delete',
1078 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1082 background_delay
=> {
1084 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1095 code
=> $update_vm_api,
1098 __PACKAGE__-
>register_method({
1099 name
=> 'update_vm',
1100 path
=> '{vmid}/config',
1104 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1106 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1109 additionalProperties
=> 0,
1110 properties
=> PVE
::QemuServer
::json_config_properties
(
1112 node
=> get_standard_option
('pve-node'),
1113 vmid
=> get_standard_option
('pve-vmid'),
1114 skiplock
=> get_standard_option
('skiplock'),
1116 type
=> 'string', format
=> 'pve-configid-list',
1117 description
=> "A list of settings you want to delete.",
1121 type
=> 'string', format
=> 'pve-configid-list',
1122 description
=> "Revert a pending change.",
1127 description
=> $opt_force_description,
1129 requires
=> 'delete',
1133 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1139 returns
=> { type
=> 'null' },
1142 &$update_vm_api($param, 1);
1148 __PACKAGE__-
>register_method({
1149 name
=> 'destroy_vm',
1154 description
=> "Destroy the vm (also delete all used/owned volumes).",
1156 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1159 additionalProperties
=> 0,
1161 node
=> get_standard_option
('pve-node'),
1162 vmid
=> get_standard_option
('pve-vmid'),
1163 skiplock
=> get_standard_option
('skiplock'),
1172 my $rpcenv = PVE
::RPCEnvironment
::get
();
1174 my $authuser = $rpcenv->get_user();
1176 my $vmid = $param->{vmid
};
1178 my $skiplock = $param->{skiplock
};
1179 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1180 if $skiplock && $authuser ne 'root@pam';
1183 my $conf = PVE
::QemuServer
::load_config
($vmid);
1185 my $storecfg = PVE
::Storage
::config
();
1187 my $delVMfromPoolFn = sub {
1188 my $usercfg = cfs_read_file
("user.cfg");
1189 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1190 if (my $data = $usercfg->{pools
}->{$pool}) {
1191 delete $data->{vms
}->{$vmid};
1192 delete $usercfg->{vms
}->{$vmid};
1193 cfs_write_file
("user.cfg", $usercfg);
1201 syslog
('info', "destroy VM $vmid: $upid\n");
1203 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1205 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1208 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1211 __PACKAGE__-
>register_method({
1213 path
=> '{vmid}/unlink',
1217 description
=> "Unlink/delete disk images.",
1219 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1222 additionalProperties
=> 0,
1224 node
=> get_standard_option
('pve-node'),
1225 vmid
=> get_standard_option
('pve-vmid'),
1227 type
=> 'string', format
=> 'pve-configid-list',
1228 description
=> "A list of disk IDs you want to delete.",
1232 description
=> $opt_force_description,
1237 returns
=> { type
=> 'null'},
1241 $param->{delete} = extract_param
($param, 'idlist');
1243 __PACKAGE__-
>update_vm($param);
1250 __PACKAGE__-
>register_method({
1252 path
=> '{vmid}/vncproxy',
1256 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1258 description
=> "Creates a TCP VNC proxy connections.",
1260 additionalProperties
=> 0,
1262 node
=> get_standard_option
('pve-node'),
1263 vmid
=> get_standard_option
('pve-vmid'),
1267 description
=> "starts websockify instead of vncproxy",
1272 additionalProperties
=> 0,
1274 user
=> { type
=> 'string' },
1275 ticket
=> { type
=> 'string' },
1276 cert
=> { type
=> 'string' },
1277 port
=> { type
=> 'integer' },
1278 upid
=> { type
=> 'string' },
1284 my $rpcenv = PVE
::RPCEnvironment
::get
();
1286 my $authuser = $rpcenv->get_user();
1288 my $vmid = $param->{vmid
};
1289 my $node = $param->{node
};
1290 my $websocket = $param->{websocket
};
1292 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1294 my $authpath = "/vms/$vmid";
1296 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1298 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1301 my $port = PVE
::Tools
::next_vnc_port
();
1306 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1307 $remip = PVE
::Cluster
::remote_node_ip
($node);
1308 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1309 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1317 syslog
('info', "starting vnc proxy $upid\n");
1321 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1323 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1325 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1326 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1327 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1328 '-timeout', $timeout, '-authpath', $authpath,
1329 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1332 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1334 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1336 my $qmstr = join(' ', @$qmcmd);
1338 # also redirect stderr (else we get RFB protocol errors)
1339 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1342 PVE
::Tools
::run_command
($cmd);
1347 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1349 PVE
::Tools
::wait_for_vnc_port
($port);
1360 __PACKAGE__-
>register_method({
1361 name
=> 'vncwebsocket',
1362 path
=> '{vmid}/vncwebsocket',
1365 description
=> "You also need to pass a valid ticket (vncticket).",
1366 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1368 description
=> "Opens a weksocket for VNC traffic.",
1370 additionalProperties
=> 0,
1372 node
=> get_standard_option
('pve-node'),
1373 vmid
=> get_standard_option
('pve-vmid'),
1375 description
=> "Ticket from previous call to vncproxy.",
1380 description
=> "Port number returned by previous vncproxy call.",
1390 port
=> { type
=> 'string' },
1396 my $rpcenv = PVE
::RPCEnvironment
::get
();
1398 my $authuser = $rpcenv->get_user();
1400 my $vmid = $param->{vmid
};
1401 my $node = $param->{node
};
1403 my $authpath = "/vms/$vmid";
1405 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1407 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1409 # Note: VNC ports are acessible from outside, so we do not gain any
1410 # security if we verify that $param->{port} belongs to VM $vmid. This
1411 # check is done by verifying the VNC ticket (inside VNC protocol).
1413 my $port = $param->{port
};
1415 return { port
=> $port };
1418 __PACKAGE__-
>register_method({
1419 name
=> 'spiceproxy',
1420 path
=> '{vmid}/spiceproxy',
1425 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1427 description
=> "Returns a SPICE configuration to connect to the VM.",
1429 additionalProperties
=> 0,
1431 node
=> get_standard_option
('pve-node'),
1432 vmid
=> get_standard_option
('pve-vmid'),
1433 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1436 returns
=> get_standard_option
('remote-viewer-config'),
1440 my $rpcenv = PVE
::RPCEnvironment
::get
();
1442 my $authuser = $rpcenv->get_user();
1444 my $vmid = $param->{vmid
};
1445 my $node = $param->{node
};
1446 my $proxy = $param->{proxy
};
1448 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1449 my $title = "VM $vmid - $conf->{'name'}",
1451 my $port = PVE
::QemuServer
::spice_port
($vmid);
1453 my ($ticket, undef, $remote_viewer_config) =
1454 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1456 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1457 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1459 return $remote_viewer_config;
1462 __PACKAGE__-
>register_method({
1464 path
=> '{vmid}/status',
1467 description
=> "Directory index",
1472 additionalProperties
=> 0,
1474 node
=> get_standard_option
('pve-node'),
1475 vmid
=> get_standard_option
('pve-vmid'),
1483 subdir
=> { type
=> 'string' },
1486 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1492 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1495 { subdir
=> 'current' },
1496 { subdir
=> 'start' },
1497 { subdir
=> 'stop' },
1503 my $vm_is_ha_managed = sub {
1506 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1507 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1513 __PACKAGE__-
>register_method({
1514 name
=> 'vm_status',
1515 path
=> '{vmid}/status/current',
1518 protected
=> 1, # qemu pid files are only readable by root
1519 description
=> "Get virtual machine status.",
1521 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1524 additionalProperties
=> 0,
1526 node
=> get_standard_option
('pve-node'),
1527 vmid
=> get_standard_option
('pve-vmid'),
1530 returns
=> { type
=> 'object' },
1535 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1537 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1538 my $status = $vmstatus->{$param->{vmid
}};
1540 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1542 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1547 __PACKAGE__-
>register_method({
1549 path
=> '{vmid}/status/start',
1553 description
=> "Start virtual machine.",
1555 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1558 additionalProperties
=> 0,
1560 node
=> get_standard_option
('pve-node'),
1561 vmid
=> get_standard_option
('pve-vmid'),
1562 skiplock
=> get_standard_option
('skiplock'),
1563 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1564 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1565 machine
=> get_standard_option
('pve-qm-machine'),
1574 my $rpcenv = PVE
::RPCEnvironment
::get
();
1576 my $authuser = $rpcenv->get_user();
1578 my $node = extract_param
($param, 'node');
1580 my $vmid = extract_param
($param, 'vmid');
1582 my $machine = extract_param
($param, 'machine');
1584 my $stateuri = extract_param
($param, 'stateuri');
1585 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1586 if $stateuri && $authuser ne 'root@pam';
1588 my $skiplock = extract_param
($param, 'skiplock');
1589 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1590 if $skiplock && $authuser ne 'root@pam';
1592 my $migratedfrom = extract_param
($param, 'migratedfrom');
1593 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1594 if $migratedfrom && $authuser ne 'root@pam';
1596 # read spice ticket from STDIN
1598 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1599 if (defined(my $line = <>)) {
1601 $spice_ticket = $line;
1605 my $storecfg = PVE
::Storage
::config
();
1607 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1608 $rpcenv->{type
} ne 'ha') {
1613 my $service = "pvevm:$vmid";
1615 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1617 print "Executing HA start for VM $vmid\n";
1619 PVE
::Tools
::run_command
($cmd);
1624 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1631 syslog
('info', "start VM $vmid: $upid\n");
1633 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1634 $machine, $spice_ticket);
1639 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1643 __PACKAGE__-
>register_method({
1645 path
=> '{vmid}/status/stop',
1649 description
=> "Stop virtual machine.",
1651 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1654 additionalProperties
=> 0,
1656 node
=> get_standard_option
('pve-node'),
1657 vmid
=> get_standard_option
('pve-vmid'),
1658 skiplock
=> get_standard_option
('skiplock'),
1659 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1661 description
=> "Wait maximal timeout seconds.",
1667 description
=> "Do not decativate storage volumes.",
1680 my $rpcenv = PVE
::RPCEnvironment
::get
();
1682 my $authuser = $rpcenv->get_user();
1684 my $node = extract_param
($param, 'node');
1686 my $vmid = extract_param
($param, 'vmid');
1688 my $skiplock = extract_param
($param, 'skiplock');
1689 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1690 if $skiplock && $authuser ne 'root@pam';
1692 my $keepActive = extract_param
($param, 'keepActive');
1693 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1694 if $keepActive && $authuser ne 'root@pam';
1696 my $migratedfrom = extract_param
($param, 'migratedfrom');
1697 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1698 if $migratedfrom && $authuser ne 'root@pam';
1701 my $storecfg = PVE
::Storage
::config
();
1703 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1708 my $service = "pvevm:$vmid";
1710 my $cmd = ['clusvcadm', '-d', $service];
1712 print "Executing HA stop for VM $vmid\n";
1714 PVE
::Tools
::run_command
($cmd);
1719 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1725 syslog
('info', "stop VM $vmid: $upid\n");
1727 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1728 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1733 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1737 __PACKAGE__-
>register_method({
1739 path
=> '{vmid}/status/reset',
1743 description
=> "Reset virtual machine.",
1745 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1748 additionalProperties
=> 0,
1750 node
=> get_standard_option
('pve-node'),
1751 vmid
=> get_standard_option
('pve-vmid'),
1752 skiplock
=> get_standard_option
('skiplock'),
1761 my $rpcenv = PVE
::RPCEnvironment
::get
();
1763 my $authuser = $rpcenv->get_user();
1765 my $node = extract_param
($param, 'node');
1767 my $vmid = extract_param
($param, 'vmid');
1769 my $skiplock = extract_param
($param, 'skiplock');
1770 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1771 if $skiplock && $authuser ne 'root@pam';
1773 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1778 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1783 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1786 __PACKAGE__-
>register_method({
1787 name
=> 'vm_shutdown',
1788 path
=> '{vmid}/status/shutdown',
1792 description
=> "Shutdown virtual machine.",
1794 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1797 additionalProperties
=> 0,
1799 node
=> get_standard_option
('pve-node'),
1800 vmid
=> get_standard_option
('pve-vmid'),
1801 skiplock
=> get_standard_option
('skiplock'),
1803 description
=> "Wait maximal timeout seconds.",
1809 description
=> "Make sure the VM stops.",
1815 description
=> "Do not decativate storage volumes.",
1828 my $rpcenv = PVE
::RPCEnvironment
::get
();
1830 my $authuser = $rpcenv->get_user();
1832 my $node = extract_param
($param, 'node');
1834 my $vmid = extract_param
($param, 'vmid');
1836 my $skiplock = extract_param
($param, 'skiplock');
1837 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1838 if $skiplock && $authuser ne 'root@pam';
1840 my $keepActive = extract_param
($param, 'keepActive');
1841 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1842 if $keepActive && $authuser ne 'root@pam';
1844 my $storecfg = PVE
::Storage
::config
();
1849 syslog
('info', "shutdown VM $vmid: $upid\n");
1851 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1852 1, $param->{forceStop
}, $keepActive);
1857 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1860 __PACKAGE__-
>register_method({
1861 name
=> 'vm_suspend',
1862 path
=> '{vmid}/status/suspend',
1866 description
=> "Suspend virtual machine.",
1868 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1871 additionalProperties
=> 0,
1873 node
=> get_standard_option
('pve-node'),
1874 vmid
=> get_standard_option
('pve-vmid'),
1875 skiplock
=> get_standard_option
('skiplock'),
1884 my $rpcenv = PVE
::RPCEnvironment
::get
();
1886 my $authuser = $rpcenv->get_user();
1888 my $node = extract_param
($param, 'node');
1890 my $vmid = extract_param
($param, 'vmid');
1892 my $skiplock = extract_param
($param, 'skiplock');
1893 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1894 if $skiplock && $authuser ne 'root@pam';
1896 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1901 syslog
('info', "suspend VM $vmid: $upid\n");
1903 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1908 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1911 __PACKAGE__-
>register_method({
1912 name
=> 'vm_resume',
1913 path
=> '{vmid}/status/resume',
1917 description
=> "Resume virtual machine.",
1919 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1922 additionalProperties
=> 0,
1924 node
=> get_standard_option
('pve-node'),
1925 vmid
=> get_standard_option
('pve-vmid'),
1926 skiplock
=> get_standard_option
('skiplock'),
1935 my $rpcenv = PVE
::RPCEnvironment
::get
();
1937 my $authuser = $rpcenv->get_user();
1939 my $node = extract_param
($param, 'node');
1941 my $vmid = extract_param
($param, 'vmid');
1943 my $skiplock = extract_param
($param, 'skiplock');
1944 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1945 if $skiplock && $authuser ne 'root@pam';
1947 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1952 syslog
('info', "resume VM $vmid: $upid\n");
1954 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1959 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1962 __PACKAGE__-
>register_method({
1963 name
=> 'vm_sendkey',
1964 path
=> '{vmid}/sendkey',
1968 description
=> "Send key event to virtual machine.",
1970 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1973 additionalProperties
=> 0,
1975 node
=> get_standard_option
('pve-node'),
1976 vmid
=> get_standard_option
('pve-vmid'),
1977 skiplock
=> get_standard_option
('skiplock'),
1979 description
=> "The key (qemu monitor encoding).",
1984 returns
=> { type
=> 'null'},
1988 my $rpcenv = PVE
::RPCEnvironment
::get
();
1990 my $authuser = $rpcenv->get_user();
1992 my $node = extract_param
($param, 'node');
1994 my $vmid = extract_param
($param, 'vmid');
1996 my $skiplock = extract_param
($param, 'skiplock');
1997 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1998 if $skiplock && $authuser ne 'root@pam';
2000 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2005 __PACKAGE__-
>register_method({
2006 name
=> 'vm_feature',
2007 path
=> '{vmid}/feature',
2011 description
=> "Check if feature for virtual machine is available.",
2013 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2016 additionalProperties
=> 0,
2018 node
=> get_standard_option
('pve-node'),
2019 vmid
=> get_standard_option
('pve-vmid'),
2021 description
=> "Feature to check.",
2023 enum
=> [ 'snapshot', 'clone', 'copy' ],
2025 snapname
=> get_standard_option
('pve-snapshot-name', {
2033 hasFeature
=> { type
=> 'boolean' },
2036 items
=> { type
=> 'string' },
2043 my $node = extract_param
($param, 'node');
2045 my $vmid = extract_param
($param, 'vmid');
2047 my $snapname = extract_param
($param, 'snapname');
2049 my $feature = extract_param
($param, 'feature');
2051 my $running = PVE
::QemuServer
::check_running
($vmid);
2053 my $conf = PVE
::QemuServer
::load_config
($vmid);
2056 my $snap = $conf->{snapshots
}->{$snapname};
2057 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2060 my $storecfg = PVE
::Storage
::config
();
2062 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2063 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2066 hasFeature
=> $hasFeature,
2067 nodes
=> [ keys %$nodelist ],
2071 __PACKAGE__-
>register_method({
2073 path
=> '{vmid}/clone',
2077 description
=> "Create a copy of virtual machine/template.",
2079 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2080 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2081 "'Datastore.AllocateSpace' on any used storage.",
2084 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2086 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2087 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2092 additionalProperties
=> 0,
2094 node
=> get_standard_option
('pve-node'),
2095 vmid
=> get_standard_option
('pve-vmid'),
2096 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2099 type
=> 'string', format
=> 'dns-name',
2100 description
=> "Set a name for the new VM.",
2105 description
=> "Description for the new VM.",
2109 type
=> 'string', format
=> 'pve-poolid',
2110 description
=> "Add the new VM to the specified pool.",
2112 snapname
=> get_standard_option
('pve-snapshot-name', {
2115 storage
=> get_standard_option
('pve-storage-id', {
2116 description
=> "Target storage for full clone.",
2121 description
=> "Target format for file storage.",
2125 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2130 description
=> "Create a full copy of all disk. This is always done when " .
2131 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2134 target
=> get_standard_option
('pve-node', {
2135 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2146 my $rpcenv = PVE
::RPCEnvironment
::get
();
2148 my $authuser = $rpcenv->get_user();
2150 my $node = extract_param
($param, 'node');
2152 my $vmid = extract_param
($param, 'vmid');
2154 my $newid = extract_param
($param, 'newid');
2156 my $pool = extract_param
($param, 'pool');
2158 if (defined($pool)) {
2159 $rpcenv->check_pool_exist($pool);
2162 my $snapname = extract_param
($param, 'snapname');
2164 my $storage = extract_param
($param, 'storage');
2166 my $format = extract_param
($param, 'format');
2168 my $target = extract_param
($param, 'target');
2170 my $localnode = PVE
::INotify
::nodename
();
2172 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2174 PVE
::Cluster
::check_node_exists
($target) if $target;
2176 my $storecfg = PVE
::Storage
::config
();
2179 # check if storage is enabled on local node
2180 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2182 # check if storage is available on target node
2183 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2184 # clone only works if target storage is shared
2185 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2186 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2190 PVE
::Cluster
::check_cfs_quorum
();
2192 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2194 # exclusive lock if VM is running - else shared lock is enough;
2195 my $shared_lock = $running ?
0 : 1;
2199 # do all tests after lock
2200 # we also try to do all tests before we fork the worker
2202 my $conf = PVE
::QemuServer
::load_config
($vmid);
2204 PVE
::QemuServer
::check_lock
($conf);
2206 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2208 die "unexpected state change\n" if $verify_running != $running;
2210 die "snapshot '$snapname' does not exist\n"
2211 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2213 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2215 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2217 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2219 my $conffile = PVE
::QemuServer
::config_file
($newid);
2221 die "unable to create VM $newid: config file already exists\n"
2224 my $newconf = { lock => 'clone' };
2228 foreach my $opt (keys %$oldconf) {
2229 my $value = $oldconf->{$opt};
2231 # do not copy snapshot related info
2232 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2233 $opt eq 'vmstate' || $opt eq 'snapstate';
2235 # always change MAC! address
2236 if ($opt =~ m/^net(\d+)$/) {
2237 my $net = PVE
::QemuServer
::parse_net
($value);
2238 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2239 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2240 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2241 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2242 die "unable to parse drive options for '$opt'\n" if !$drive;
2243 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2244 $newconf->{$opt} = $value; # simply copy configuration
2246 if ($param->{full
}) {
2247 die "Full clone feature is not available"
2248 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2251 # not full means clone instead of copy
2252 die "Linked clone feature is not available"
2253 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2255 $drives->{$opt} = $drive;
2256 push @$vollist, $drive->{file
};
2259 # copy everything else
2260 $newconf->{$opt} = $value;
2264 # auto generate a new uuid
2265 my ($uuid, $uuid_str);
2266 UUID
::generate
($uuid);
2267 UUID
::unparse
($uuid, $uuid_str);
2268 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2269 $smbios1->{uuid
} = $uuid_str;
2270 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2272 delete $newconf->{template
};
2274 if ($param->{name
}) {
2275 $newconf->{name
} = $param->{name
};
2277 if ($oldconf->{name
}) {
2278 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2280 $newconf->{name
} = "Copy-of-VM-$vmid";
2284 if ($param->{description
}) {
2285 $newconf->{description
} = $param->{description
};
2288 # create empty/temp config - this fails if VM already exists on other node
2289 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2294 my $newvollist = [];
2297 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2299 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2301 foreach my $opt (keys %$drives) {
2302 my $drive = $drives->{$opt};
2304 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2305 $newid, $storage, $format, $drive->{full
}, $newvollist);
2307 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2309 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2312 delete $newconf->{lock};
2313 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2316 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2317 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2319 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2320 die "Failed to move config to node '$target' - rename failed: $!\n"
2321 if !rename($conffile, $newconffile);
2324 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2329 sleep 1; # some storage like rbd need to wait before release volume - really?
2331 foreach my $volid (@$newvollist) {
2332 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2335 die "clone failed: $err";
2341 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2344 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2345 # Aquire exclusive lock lock for $newid
2346 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2351 __PACKAGE__-
>register_method({
2352 name
=> 'move_vm_disk',
2353 path
=> '{vmid}/move_disk',
2357 description
=> "Move volume to different storage.",
2359 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2360 "and 'Datastore.AllocateSpace' permissions on the storage.",
2363 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2364 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2368 additionalProperties
=> 0,
2370 node
=> get_standard_option
('pve-node'),
2371 vmid
=> get_standard_option
('pve-vmid'),
2374 description
=> "The disk you want to move.",
2375 enum
=> [ PVE
::QemuServer
::disknames
() ],
2377 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2380 description
=> "Target Format.",
2381 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2386 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2392 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2400 description
=> "the task ID.",
2405 my $rpcenv = PVE
::RPCEnvironment
::get
();
2407 my $authuser = $rpcenv->get_user();
2409 my $node = extract_param
($param, 'node');
2411 my $vmid = extract_param
($param, 'vmid');
2413 my $digest = extract_param
($param, 'digest');
2415 my $disk = extract_param
($param, 'disk');
2417 my $storeid = extract_param
($param, 'storage');
2419 my $format = extract_param
($param, 'format');
2421 my $storecfg = PVE
::Storage
::config
();
2423 my $updatefn = sub {
2425 my $conf = PVE
::QemuServer
::load_config
($vmid);
2427 die "checksum missmatch (file change by other user?)\n"
2428 if $digest && $digest ne $conf->{digest
};
2430 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2432 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2434 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2436 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2439 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2440 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2444 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2445 (!$format || !$oldfmt || $oldfmt eq $format);
2447 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2449 my $running = PVE
::QemuServer
::check_running
($vmid);
2451 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2455 my $newvollist = [];
2458 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2460 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2461 $vmid, $storeid, $format, 1, $newvollist);
2463 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2465 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2467 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2470 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2471 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2478 foreach my $volid (@$newvollist) {
2479 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2482 die "storage migration failed: $err";
2485 if ($param->{delete}) {
2486 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2487 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2488 if ($used_paths->{$path}){
2489 warn "volume $old_volid have snapshots. Can't delete it\n";
2490 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2491 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2493 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2499 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2502 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2505 __PACKAGE__-
>register_method({
2506 name
=> 'migrate_vm',
2507 path
=> '{vmid}/migrate',
2511 description
=> "Migrate virtual machine. Creates a new migration task.",
2513 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2516 additionalProperties
=> 0,
2518 node
=> get_standard_option
('pve-node'),
2519 vmid
=> get_standard_option
('pve-vmid'),
2520 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2523 description
=> "Use online/live migration.",
2528 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2535 description
=> "the task ID.",
2540 my $rpcenv = PVE
::RPCEnvironment
::get
();
2542 my $authuser = $rpcenv->get_user();
2544 my $target = extract_param
($param, 'target');
2546 my $localnode = PVE
::INotify
::nodename
();
2547 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2549 PVE
::Cluster
::check_cfs_quorum
();
2551 PVE
::Cluster
::check_node_exists
($target);
2553 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2555 my $vmid = extract_param
($param, 'vmid');
2557 raise_param_exc
({ force
=> "Only root may use this option." })
2558 if $param->{force
} && $authuser ne 'root@pam';
2561 my $conf = PVE
::QemuServer
::load_config
($vmid);
2563 # try to detect errors early
2565 PVE
::QemuServer
::check_lock
($conf);
2567 if (PVE
::QemuServer
::check_running
($vmid)) {
2568 die "cant migrate running VM without --online\n"
2569 if !$param->{online
};
2572 my $storecfg = PVE
::Storage
::config
();
2573 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2575 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2580 my $service = "pvevm:$vmid";
2582 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2584 print "Executing HA migrate for VM $vmid to node $target\n";
2586 PVE
::Tools
::run_command
($cmd);
2591 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2598 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2601 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2606 __PACKAGE__-
>register_method({
2608 path
=> '{vmid}/monitor',
2612 description
=> "Execute Qemu monitor commands.",
2614 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2617 additionalProperties
=> 0,
2619 node
=> get_standard_option
('pve-node'),
2620 vmid
=> get_standard_option
('pve-vmid'),
2623 description
=> "The monitor command.",
2627 returns
=> { type
=> 'string'},
2631 my $vmid = $param->{vmid
};
2633 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2637 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2639 $res = "ERROR: $@" if $@;
2644 __PACKAGE__-
>register_method({
2645 name
=> 'resize_vm',
2646 path
=> '{vmid}/resize',
2650 description
=> "Extend volume size.",
2652 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2655 additionalProperties
=> 0,
2657 node
=> get_standard_option
('pve-node'),
2658 vmid
=> get_standard_option
('pve-vmid'),
2659 skiplock
=> get_standard_option
('skiplock'),
2662 description
=> "The disk you want to resize.",
2663 enum
=> [PVE
::QemuServer
::disknames
()],
2667 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2668 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.",
2672 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2678 returns
=> { type
=> 'null'},
2682 my $rpcenv = PVE
::RPCEnvironment
::get
();
2684 my $authuser = $rpcenv->get_user();
2686 my $node = extract_param
($param, 'node');
2688 my $vmid = extract_param
($param, 'vmid');
2690 my $digest = extract_param
($param, 'digest');
2692 my $disk = extract_param
($param, 'disk');
2694 my $sizestr = extract_param
($param, 'size');
2696 my $skiplock = extract_param
($param, 'skiplock');
2697 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2698 if $skiplock && $authuser ne 'root@pam';
2700 my $storecfg = PVE
::Storage
::config
();
2702 my $updatefn = sub {
2704 my $conf = PVE
::QemuServer
::load_config
($vmid);
2706 die "checksum missmatch (file change by other user?)\n"
2707 if $digest && $digest ne $conf->{digest
};
2708 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2710 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2712 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2714 my $volid = $drive->{file
};
2716 die "disk '$disk' has no associated volume\n" if !$volid;
2718 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2720 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2722 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2724 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2726 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2727 my ($ext, $newsize, $unit) = ($1, $2, $4);
2730 $newsize = $newsize * 1024;
2731 } elsif ($unit eq 'M') {
2732 $newsize = $newsize * 1024 * 1024;
2733 } elsif ($unit eq 'G') {
2734 $newsize = $newsize * 1024 * 1024 * 1024;
2735 } elsif ($unit eq 'T') {
2736 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2739 $newsize += $size if $ext;
2740 $newsize = int($newsize);
2742 die "unable to skrink disk size\n" if $newsize < $size;
2744 return if $size == $newsize;
2746 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2748 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2750 $drive->{size
} = $newsize;
2751 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2753 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2756 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2760 __PACKAGE__-
>register_method({
2761 name
=> 'snapshot_list',
2762 path
=> '{vmid}/snapshot',
2764 description
=> "List all snapshots.",
2766 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2769 protected
=> 1, # qemu pid files are only readable by root
2771 additionalProperties
=> 0,
2773 vmid
=> get_standard_option
('pve-vmid'),
2774 node
=> get_standard_option
('pve-node'),
2783 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2788 my $vmid = $param->{vmid
};
2790 my $conf = PVE
::QemuServer
::load_config
($vmid);
2791 my $snaphash = $conf->{snapshots
} || {};
2795 foreach my $name (keys %$snaphash) {
2796 my $d = $snaphash->{$name};
2799 snaptime
=> $d->{snaptime
} || 0,
2800 vmstate
=> $d->{vmstate
} ?
1 : 0,
2801 description
=> $d->{description
} || '',
2803 $item->{parent
} = $d->{parent
} if $d->{parent
};
2804 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2808 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2809 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2810 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2812 push @$res, $current;
2817 __PACKAGE__-
>register_method({
2819 path
=> '{vmid}/snapshot',
2823 description
=> "Snapshot a VM.",
2825 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2828 additionalProperties
=> 0,
2830 node
=> get_standard_option
('pve-node'),
2831 vmid
=> get_standard_option
('pve-vmid'),
2832 snapname
=> get_standard_option
('pve-snapshot-name'),
2836 description
=> "Save the vmstate",
2841 description
=> "A textual description or comment.",
2847 description
=> "the task ID.",
2852 my $rpcenv = PVE
::RPCEnvironment
::get
();
2854 my $authuser = $rpcenv->get_user();
2856 my $node = extract_param
($param, 'node');
2858 my $vmid = extract_param
($param, 'vmid');
2860 my $snapname = extract_param
($param, 'snapname');
2862 die "unable to use snapshot name 'current' (reserved name)\n"
2863 if $snapname eq 'current';
2866 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2867 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2868 $param->{description
});
2871 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2874 __PACKAGE__-
>register_method({
2875 name
=> 'snapshot_cmd_idx',
2876 path
=> '{vmid}/snapshot/{snapname}',
2883 additionalProperties
=> 0,
2885 vmid
=> get_standard_option
('pve-vmid'),
2886 node
=> get_standard_option
('pve-node'),
2887 snapname
=> get_standard_option
('pve-snapshot-name'),
2896 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2903 push @$res, { cmd
=> 'rollback' };
2904 push @$res, { cmd
=> 'config' };
2909 __PACKAGE__-
>register_method({
2910 name
=> 'update_snapshot_config',
2911 path
=> '{vmid}/snapshot/{snapname}/config',
2915 description
=> "Update snapshot metadata.",
2917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2920 additionalProperties
=> 0,
2922 node
=> get_standard_option
('pve-node'),
2923 vmid
=> get_standard_option
('pve-vmid'),
2924 snapname
=> get_standard_option
('pve-snapshot-name'),
2928 description
=> "A textual description or comment.",
2932 returns
=> { type
=> 'null' },
2936 my $rpcenv = PVE
::RPCEnvironment
::get
();
2938 my $authuser = $rpcenv->get_user();
2940 my $vmid = extract_param
($param, 'vmid');
2942 my $snapname = extract_param
($param, 'snapname');
2944 return undef if !defined($param->{description
});
2946 my $updatefn = sub {
2948 my $conf = PVE
::QemuServer
::load_config
($vmid);
2950 PVE
::QemuServer
::check_lock
($conf);
2952 my $snap = $conf->{snapshots
}->{$snapname};
2954 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2956 $snap->{description
} = $param->{description
} if defined($param->{description
});
2958 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2961 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2966 __PACKAGE__-
>register_method({
2967 name
=> 'get_snapshot_config',
2968 path
=> '{vmid}/snapshot/{snapname}/config',
2971 description
=> "Get snapshot configuration",
2973 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2976 additionalProperties
=> 0,
2978 node
=> get_standard_option
('pve-node'),
2979 vmid
=> get_standard_option
('pve-vmid'),
2980 snapname
=> get_standard_option
('pve-snapshot-name'),
2983 returns
=> { type
=> "object" },
2987 my $rpcenv = PVE
::RPCEnvironment
::get
();
2989 my $authuser = $rpcenv->get_user();
2991 my $vmid = extract_param
($param, 'vmid');
2993 my $snapname = extract_param
($param, 'snapname');
2995 my $conf = PVE
::QemuServer
::load_config
($vmid);
2997 my $snap = $conf->{snapshots
}->{$snapname};
2999 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3004 __PACKAGE__-
>register_method({
3006 path
=> '{vmid}/snapshot/{snapname}/rollback',
3010 description
=> "Rollback VM state to specified snapshot.",
3012 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3015 additionalProperties
=> 0,
3017 node
=> get_standard_option
('pve-node'),
3018 vmid
=> get_standard_option
('pve-vmid'),
3019 snapname
=> get_standard_option
('pve-snapshot-name'),
3024 description
=> "the task ID.",
3029 my $rpcenv = PVE
::RPCEnvironment
::get
();
3031 my $authuser = $rpcenv->get_user();
3033 my $node = extract_param
($param, 'node');
3035 my $vmid = extract_param
($param, 'vmid');
3037 my $snapname = extract_param
($param, 'snapname');
3040 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3041 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3044 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3047 __PACKAGE__-
>register_method({
3048 name
=> 'delsnapshot',
3049 path
=> '{vmid}/snapshot/{snapname}',
3053 description
=> "Delete a VM snapshot.",
3055 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3058 additionalProperties
=> 0,
3060 node
=> get_standard_option
('pve-node'),
3061 vmid
=> get_standard_option
('pve-vmid'),
3062 snapname
=> get_standard_option
('pve-snapshot-name'),
3066 description
=> "For removal from config file, even if removing disk snapshots fails.",
3072 description
=> "the task ID.",
3077 my $rpcenv = PVE
::RPCEnvironment
::get
();
3079 my $authuser = $rpcenv->get_user();
3081 my $node = extract_param
($param, 'node');
3083 my $vmid = extract_param
($param, 'vmid');
3085 my $snapname = extract_param
($param, 'snapname');
3088 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3089 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3092 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3095 __PACKAGE__-
>register_method({
3097 path
=> '{vmid}/template',
3101 description
=> "Create a Template.",
3103 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3104 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3107 additionalProperties
=> 0,
3109 node
=> get_standard_option
('pve-node'),
3110 vmid
=> get_standard_option
('pve-vmid'),
3114 description
=> "If you want to convert only 1 disk to base image.",
3115 enum
=> [PVE
::QemuServer
::disknames
()],
3120 returns
=> { type
=> 'null'},
3124 my $rpcenv = PVE
::RPCEnvironment
::get
();
3126 my $authuser = $rpcenv->get_user();
3128 my $node = extract_param
($param, 'node');
3130 my $vmid = extract_param
($param, 'vmid');
3132 my $disk = extract_param
($param, 'disk');
3134 my $updatefn = sub {
3136 my $conf = PVE
::QemuServer
::load_config
($vmid);
3138 PVE
::QemuServer
::check_lock
($conf);
3140 die "unable to create template, because VM contains snapshots\n"
3141 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3143 die "you can't convert a template to a template\n"
3144 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3146 die "you can't convert a VM to template if VM is running\n"
3147 if PVE
::QemuServer
::check_running
($vmid);
3150 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3153 $conf->{template
} = 1;
3154 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3156 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3159 PVE
::QemuServer
::lock_config
($vmid, $updatefn);