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 $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 $force = extract_param
($param, 'force');
829 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
831 my $storecfg = PVE
::Storage
::config
();
833 my $defaults = PVE
::QemuServer
::load_defaults
();
835 &$resolve_cdrom_alias($param);
837 # now try to verify all parameters
840 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
841 $opt = 'ide2' if $opt eq 'cdrom';
842 raise_param_exc
({ delete => "you can't use '-$opt' and " .
843 "-delete $opt' at the same time" })
844 if defined($param->{$opt});
846 if (!PVE
::QemuServer
::option_exists
($opt)) {
847 raise_param_exc
({ delete => "unknown option '$opt'" });
853 foreach my $opt (keys %$param) {
854 if (PVE
::QemuServer
::valid_drivename
($opt)) {
856 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
857 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
858 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
859 } elsif ($opt =~ m/^net(\d+)$/) {
861 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
862 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
866 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
868 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
870 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
874 my $conf = PVE
::QemuServer
::load_config
($vmid);
876 die "checksum missmatch (file change by other user?)\n"
877 if $digest && $digest ne $conf->{digest
};
879 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
881 if ($param->{memory
} || defined($param->{balloon
})) {
882 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
883 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
885 die "balloon value too large (must be smaller than assigned memory)\n"
886 if $balloon && $balloon > $maxmem;
889 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
893 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
895 # write updates to pending section
897 my $modified = {}; # record what $option we modify
899 foreach my $opt (@delete) {
900 $modified->{$opt} = 1;
901 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
902 if ($opt =~ m/^unused/) {
903 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
904 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
905 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
906 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
907 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
908 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
910 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
911 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
912 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
913 if defined($conf->{pending
}->{$opt});
914 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
915 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
917 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
918 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
922 foreach my $opt (keys %$param) { # add/change
923 $modified->{$opt} = 1;
924 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
925 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
927 if (PVE
::QemuServer
::valid_drivename
($opt)) {
928 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
929 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
930 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
932 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
934 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
935 if defined($conf->{pending
}->{$opt});
937 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
939 $conf->{pending
}->{$opt} = $param->{$opt};
941 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
942 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
945 # remove pending changes when nothing changed
946 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
947 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
948 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
950 return if !scalar(keys %{$conf->{pending
}});
952 my $running = PVE
::QemuServer
::check_running
($vmid);
954 # apply pending changes
956 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
960 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
961 raise_param_exc
($errors) if scalar(keys %$errors);
963 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
973 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
975 if ($background_delay) {
977 # Note: It would be better to do that in the Event based HTTPServer
978 # to avoid blocking call to sleep.
980 my $end_time = time() + $background_delay;
982 my $task = PVE
::Tools
::upid_decode
($upid);
985 while (time() < $end_time) {
986 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
988 sleep(1); # this gets interrupted when child process ends
992 my $status = PVE
::Tools
::upid_read_status
($upid);
993 return undef if $status eq 'OK';
1002 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1005 my $vm_config_perm_list = [
1010 'VM.Config.Network',
1012 'VM.Config.Options',
1015 __PACKAGE__-
>register_method({
1016 name
=> 'update_vm_async',
1017 path
=> '{vmid}/config',
1021 description
=> "Set virtual machine options (asynchrounous API).",
1023 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1026 additionalProperties
=> 0,
1027 properties
=> PVE
::QemuServer
::json_config_properties
(
1029 node
=> get_standard_option
('pve-node'),
1030 vmid
=> get_standard_option
('pve-vmid'),
1031 skiplock
=> get_standard_option
('skiplock'),
1033 type
=> 'string', format
=> 'pve-configid-list',
1034 description
=> "A list of settings you want to delete.",
1039 description
=> $opt_force_description,
1041 requires
=> 'delete',
1045 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1049 background_delay
=> {
1051 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1062 code
=> $update_vm_api,
1065 __PACKAGE__-
>register_method({
1066 name
=> 'update_vm',
1067 path
=> '{vmid}/config',
1071 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1073 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1076 additionalProperties
=> 0,
1077 properties
=> PVE
::QemuServer
::json_config_properties
(
1079 node
=> get_standard_option
('pve-node'),
1080 vmid
=> get_standard_option
('pve-vmid'),
1081 skiplock
=> get_standard_option
('skiplock'),
1083 type
=> 'string', format
=> 'pve-configid-list',
1084 description
=> "A list of settings you want to delete.",
1089 description
=> $opt_force_description,
1091 requires
=> 'delete',
1095 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1101 returns
=> { type
=> 'null' },
1104 &$update_vm_api($param, 1);
1110 __PACKAGE__-
>register_method({
1111 name
=> 'destroy_vm',
1116 description
=> "Destroy the vm (also delete all used/owned volumes).",
1118 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1121 additionalProperties
=> 0,
1123 node
=> get_standard_option
('pve-node'),
1124 vmid
=> get_standard_option
('pve-vmid'),
1125 skiplock
=> get_standard_option
('skiplock'),
1134 my $rpcenv = PVE
::RPCEnvironment
::get
();
1136 my $authuser = $rpcenv->get_user();
1138 my $vmid = $param->{vmid
};
1140 my $skiplock = $param->{skiplock
};
1141 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1142 if $skiplock && $authuser ne 'root@pam';
1145 my $conf = PVE
::QemuServer
::load_config
($vmid);
1147 my $storecfg = PVE
::Storage
::config
();
1149 my $delVMfromPoolFn = sub {
1150 my $usercfg = cfs_read_file
("user.cfg");
1151 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1152 if (my $data = $usercfg->{pools
}->{$pool}) {
1153 delete $data->{vms
}->{$vmid};
1154 delete $usercfg->{vms
}->{$vmid};
1155 cfs_write_file
("user.cfg", $usercfg);
1163 syslog
('info', "destroy VM $vmid: $upid\n");
1165 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1167 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1170 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1173 __PACKAGE__-
>register_method({
1175 path
=> '{vmid}/unlink',
1179 description
=> "Unlink/delete disk images.",
1181 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1184 additionalProperties
=> 0,
1186 node
=> get_standard_option
('pve-node'),
1187 vmid
=> get_standard_option
('pve-vmid'),
1189 type
=> 'string', format
=> 'pve-configid-list',
1190 description
=> "A list of disk IDs you want to delete.",
1194 description
=> $opt_force_description,
1199 returns
=> { type
=> 'null'},
1203 $param->{delete} = extract_param
($param, 'idlist');
1205 __PACKAGE__-
>update_vm($param);
1212 __PACKAGE__-
>register_method({
1214 path
=> '{vmid}/vncproxy',
1218 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1220 description
=> "Creates a TCP VNC proxy connections.",
1222 additionalProperties
=> 0,
1224 node
=> get_standard_option
('pve-node'),
1225 vmid
=> get_standard_option
('pve-vmid'),
1229 description
=> "starts websockify instead of vncproxy",
1234 additionalProperties
=> 0,
1236 user
=> { type
=> 'string' },
1237 ticket
=> { type
=> 'string' },
1238 cert
=> { type
=> 'string' },
1239 port
=> { type
=> 'integer' },
1240 upid
=> { type
=> 'string' },
1246 my $rpcenv = PVE
::RPCEnvironment
::get
();
1248 my $authuser = $rpcenv->get_user();
1250 my $vmid = $param->{vmid
};
1251 my $node = $param->{node
};
1252 my $websocket = $param->{websocket
};
1254 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1256 my $authpath = "/vms/$vmid";
1258 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1260 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1263 my $port = PVE
::Tools
::next_vnc_port
();
1268 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1269 $remip = PVE
::Cluster
::remote_node_ip
($node);
1270 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1271 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1279 syslog
('info', "starting vnc proxy $upid\n");
1283 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1285 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1287 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1288 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1289 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1290 '-timeout', $timeout, '-authpath', $authpath,
1291 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1294 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1296 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1298 my $qmstr = join(' ', @$qmcmd);
1300 # also redirect stderr (else we get RFB protocol errors)
1301 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1304 PVE
::Tools
::run_command
($cmd);
1309 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1311 PVE
::Tools
::wait_for_vnc_port
($port);
1322 __PACKAGE__-
>register_method({
1323 name
=> 'vncwebsocket',
1324 path
=> '{vmid}/vncwebsocket',
1327 description
=> "You also need to pass a valid ticket (vncticket).",
1328 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1330 description
=> "Opens a weksocket for VNC traffic.",
1332 additionalProperties
=> 0,
1334 node
=> get_standard_option
('pve-node'),
1335 vmid
=> get_standard_option
('pve-vmid'),
1337 description
=> "Ticket from previous call to vncproxy.",
1342 description
=> "Port number returned by previous vncproxy call.",
1352 port
=> { type
=> 'string' },
1358 my $rpcenv = PVE
::RPCEnvironment
::get
();
1360 my $authuser = $rpcenv->get_user();
1362 my $vmid = $param->{vmid
};
1363 my $node = $param->{node
};
1365 my $authpath = "/vms/$vmid";
1367 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1369 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1371 # Note: VNC ports are acessible from outside, so we do not gain any
1372 # security if we verify that $param->{port} belongs to VM $vmid. This
1373 # check is done by verifying the VNC ticket (inside VNC protocol).
1375 my $port = $param->{port
};
1377 return { port
=> $port };
1380 __PACKAGE__-
>register_method({
1381 name
=> 'spiceproxy',
1382 path
=> '{vmid}/spiceproxy',
1387 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1389 description
=> "Returns a SPICE configuration to connect to the VM.",
1391 additionalProperties
=> 0,
1393 node
=> get_standard_option
('pve-node'),
1394 vmid
=> get_standard_option
('pve-vmid'),
1395 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1398 returns
=> get_standard_option
('remote-viewer-config'),
1402 my $rpcenv = PVE
::RPCEnvironment
::get
();
1404 my $authuser = $rpcenv->get_user();
1406 my $vmid = $param->{vmid
};
1407 my $node = $param->{node
};
1408 my $proxy = $param->{proxy
};
1410 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1411 my $title = "VM $vmid - $conf->{'name'}",
1413 my $port = PVE
::QemuServer
::spice_port
($vmid);
1415 my ($ticket, undef, $remote_viewer_config) =
1416 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1418 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1419 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1421 return $remote_viewer_config;
1424 __PACKAGE__-
>register_method({
1426 path
=> '{vmid}/status',
1429 description
=> "Directory index",
1434 additionalProperties
=> 0,
1436 node
=> get_standard_option
('pve-node'),
1437 vmid
=> get_standard_option
('pve-vmid'),
1445 subdir
=> { type
=> 'string' },
1448 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1454 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1457 { subdir
=> 'current' },
1458 { subdir
=> 'start' },
1459 { subdir
=> 'stop' },
1465 my $vm_is_ha_managed = sub {
1468 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1469 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1475 __PACKAGE__-
>register_method({
1476 name
=> 'vm_status',
1477 path
=> '{vmid}/status/current',
1480 protected
=> 1, # qemu pid files are only readable by root
1481 description
=> "Get virtual machine status.",
1483 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1486 additionalProperties
=> 0,
1488 node
=> get_standard_option
('pve-node'),
1489 vmid
=> get_standard_option
('pve-vmid'),
1492 returns
=> { type
=> 'object' },
1497 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1499 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1500 my $status = $vmstatus->{$param->{vmid
}};
1502 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1504 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1509 __PACKAGE__-
>register_method({
1511 path
=> '{vmid}/status/start',
1515 description
=> "Start virtual machine.",
1517 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1520 additionalProperties
=> 0,
1522 node
=> get_standard_option
('pve-node'),
1523 vmid
=> get_standard_option
('pve-vmid'),
1524 skiplock
=> get_standard_option
('skiplock'),
1525 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1526 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1527 machine
=> get_standard_option
('pve-qm-machine'),
1536 my $rpcenv = PVE
::RPCEnvironment
::get
();
1538 my $authuser = $rpcenv->get_user();
1540 my $node = extract_param
($param, 'node');
1542 my $vmid = extract_param
($param, 'vmid');
1544 my $machine = extract_param
($param, 'machine');
1546 my $stateuri = extract_param
($param, 'stateuri');
1547 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1548 if $stateuri && $authuser ne 'root@pam';
1550 my $skiplock = extract_param
($param, 'skiplock');
1551 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1552 if $skiplock && $authuser ne 'root@pam';
1554 my $migratedfrom = extract_param
($param, 'migratedfrom');
1555 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1556 if $migratedfrom && $authuser ne 'root@pam';
1558 # read spice ticket from STDIN
1560 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1561 if (defined(my $line = <>)) {
1563 $spice_ticket = $line;
1567 my $storecfg = PVE
::Storage
::config
();
1569 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1570 $rpcenv->{type
} ne 'ha') {
1575 my $service = "pvevm:$vmid";
1577 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1579 print "Executing HA start for VM $vmid\n";
1581 PVE
::Tools
::run_command
($cmd);
1586 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1593 syslog
('info', "start VM $vmid: $upid\n");
1595 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1596 $machine, $spice_ticket);
1601 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1605 __PACKAGE__-
>register_method({
1607 path
=> '{vmid}/status/stop',
1611 description
=> "Stop virtual machine.",
1613 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1616 additionalProperties
=> 0,
1618 node
=> get_standard_option
('pve-node'),
1619 vmid
=> get_standard_option
('pve-vmid'),
1620 skiplock
=> get_standard_option
('skiplock'),
1621 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1623 description
=> "Wait maximal timeout seconds.",
1629 description
=> "Do not decativate storage volumes.",
1642 my $rpcenv = PVE
::RPCEnvironment
::get
();
1644 my $authuser = $rpcenv->get_user();
1646 my $node = extract_param
($param, 'node');
1648 my $vmid = extract_param
($param, 'vmid');
1650 my $skiplock = extract_param
($param, 'skiplock');
1651 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1652 if $skiplock && $authuser ne 'root@pam';
1654 my $keepActive = extract_param
($param, 'keepActive');
1655 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1656 if $keepActive && $authuser ne 'root@pam';
1658 my $migratedfrom = extract_param
($param, 'migratedfrom');
1659 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1660 if $migratedfrom && $authuser ne 'root@pam';
1663 my $storecfg = PVE
::Storage
::config
();
1665 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1670 my $service = "pvevm:$vmid";
1672 my $cmd = ['clusvcadm', '-d', $service];
1674 print "Executing HA stop for VM $vmid\n";
1676 PVE
::Tools
::run_command
($cmd);
1681 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1687 syslog
('info', "stop VM $vmid: $upid\n");
1689 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1690 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1695 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1699 __PACKAGE__-
>register_method({
1701 path
=> '{vmid}/status/reset',
1705 description
=> "Reset virtual machine.",
1707 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1710 additionalProperties
=> 0,
1712 node
=> get_standard_option
('pve-node'),
1713 vmid
=> get_standard_option
('pve-vmid'),
1714 skiplock
=> get_standard_option
('skiplock'),
1723 my $rpcenv = PVE
::RPCEnvironment
::get
();
1725 my $authuser = $rpcenv->get_user();
1727 my $node = extract_param
($param, 'node');
1729 my $vmid = extract_param
($param, 'vmid');
1731 my $skiplock = extract_param
($param, 'skiplock');
1732 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1733 if $skiplock && $authuser ne 'root@pam';
1735 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1740 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1745 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1748 __PACKAGE__-
>register_method({
1749 name
=> 'vm_shutdown',
1750 path
=> '{vmid}/status/shutdown',
1754 description
=> "Shutdown virtual machine.",
1756 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1759 additionalProperties
=> 0,
1761 node
=> get_standard_option
('pve-node'),
1762 vmid
=> get_standard_option
('pve-vmid'),
1763 skiplock
=> get_standard_option
('skiplock'),
1765 description
=> "Wait maximal timeout seconds.",
1771 description
=> "Make sure the VM stops.",
1777 description
=> "Do not decativate storage volumes.",
1790 my $rpcenv = PVE
::RPCEnvironment
::get
();
1792 my $authuser = $rpcenv->get_user();
1794 my $node = extract_param
($param, 'node');
1796 my $vmid = extract_param
($param, 'vmid');
1798 my $skiplock = extract_param
($param, 'skiplock');
1799 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1800 if $skiplock && $authuser ne 'root@pam';
1802 my $keepActive = extract_param
($param, 'keepActive');
1803 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1804 if $keepActive && $authuser ne 'root@pam';
1806 my $storecfg = PVE
::Storage
::config
();
1811 syslog
('info', "shutdown VM $vmid: $upid\n");
1813 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1814 1, $param->{forceStop
}, $keepActive);
1819 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1822 __PACKAGE__-
>register_method({
1823 name
=> 'vm_suspend',
1824 path
=> '{vmid}/status/suspend',
1828 description
=> "Suspend virtual machine.",
1830 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1833 additionalProperties
=> 0,
1835 node
=> get_standard_option
('pve-node'),
1836 vmid
=> get_standard_option
('pve-vmid'),
1837 skiplock
=> get_standard_option
('skiplock'),
1846 my $rpcenv = PVE
::RPCEnvironment
::get
();
1848 my $authuser = $rpcenv->get_user();
1850 my $node = extract_param
($param, 'node');
1852 my $vmid = extract_param
($param, 'vmid');
1854 my $skiplock = extract_param
($param, 'skiplock');
1855 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1856 if $skiplock && $authuser ne 'root@pam';
1858 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1863 syslog
('info', "suspend VM $vmid: $upid\n");
1865 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1870 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1873 __PACKAGE__-
>register_method({
1874 name
=> 'vm_resume',
1875 path
=> '{vmid}/status/resume',
1879 description
=> "Resume virtual machine.",
1881 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1884 additionalProperties
=> 0,
1886 node
=> get_standard_option
('pve-node'),
1887 vmid
=> get_standard_option
('pve-vmid'),
1888 skiplock
=> get_standard_option
('skiplock'),
1897 my $rpcenv = PVE
::RPCEnvironment
::get
();
1899 my $authuser = $rpcenv->get_user();
1901 my $node = extract_param
($param, 'node');
1903 my $vmid = extract_param
($param, 'vmid');
1905 my $skiplock = extract_param
($param, 'skiplock');
1906 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1907 if $skiplock && $authuser ne 'root@pam';
1909 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1914 syslog
('info', "resume VM $vmid: $upid\n");
1916 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1921 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1924 __PACKAGE__-
>register_method({
1925 name
=> 'vm_sendkey',
1926 path
=> '{vmid}/sendkey',
1930 description
=> "Send key event to virtual machine.",
1932 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1935 additionalProperties
=> 0,
1937 node
=> get_standard_option
('pve-node'),
1938 vmid
=> get_standard_option
('pve-vmid'),
1939 skiplock
=> get_standard_option
('skiplock'),
1941 description
=> "The key (qemu monitor encoding).",
1946 returns
=> { type
=> 'null'},
1950 my $rpcenv = PVE
::RPCEnvironment
::get
();
1952 my $authuser = $rpcenv->get_user();
1954 my $node = extract_param
($param, 'node');
1956 my $vmid = extract_param
($param, 'vmid');
1958 my $skiplock = extract_param
($param, 'skiplock');
1959 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1960 if $skiplock && $authuser ne 'root@pam';
1962 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1967 __PACKAGE__-
>register_method({
1968 name
=> 'vm_feature',
1969 path
=> '{vmid}/feature',
1973 description
=> "Check if feature for virtual machine is available.",
1975 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1978 additionalProperties
=> 0,
1980 node
=> get_standard_option
('pve-node'),
1981 vmid
=> get_standard_option
('pve-vmid'),
1983 description
=> "Feature to check.",
1985 enum
=> [ 'snapshot', 'clone', 'copy' ],
1987 snapname
=> get_standard_option
('pve-snapshot-name', {
1995 hasFeature
=> { type
=> 'boolean' },
1998 items
=> { type
=> 'string' },
2005 my $node = extract_param
($param, 'node');
2007 my $vmid = extract_param
($param, 'vmid');
2009 my $snapname = extract_param
($param, 'snapname');
2011 my $feature = extract_param
($param, 'feature');
2013 my $running = PVE
::QemuServer
::check_running
($vmid);
2015 my $conf = PVE
::QemuServer
::load_config
($vmid);
2018 my $snap = $conf->{snapshots
}->{$snapname};
2019 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2022 my $storecfg = PVE
::Storage
::config
();
2024 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2025 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2028 hasFeature
=> $hasFeature,
2029 nodes
=> [ keys %$nodelist ],
2033 __PACKAGE__-
>register_method({
2035 path
=> '{vmid}/clone',
2039 description
=> "Create a copy of virtual machine/template.",
2041 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2042 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2043 "'Datastore.AllocateSpace' on any used storage.",
2046 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2048 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2049 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2054 additionalProperties
=> 0,
2056 node
=> get_standard_option
('pve-node'),
2057 vmid
=> get_standard_option
('pve-vmid'),
2058 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2061 type
=> 'string', format
=> 'dns-name',
2062 description
=> "Set a name for the new VM.",
2067 description
=> "Description for the new VM.",
2071 type
=> 'string', format
=> 'pve-poolid',
2072 description
=> "Add the new VM to the specified pool.",
2074 snapname
=> get_standard_option
('pve-snapshot-name', {
2077 storage
=> get_standard_option
('pve-storage-id', {
2078 description
=> "Target storage for full clone.",
2083 description
=> "Target format for file storage.",
2087 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2092 description
=> "Create a full copy of all disk. This is always done when " .
2093 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2096 target
=> get_standard_option
('pve-node', {
2097 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2108 my $rpcenv = PVE
::RPCEnvironment
::get
();
2110 my $authuser = $rpcenv->get_user();
2112 my $node = extract_param
($param, 'node');
2114 my $vmid = extract_param
($param, 'vmid');
2116 my $newid = extract_param
($param, 'newid');
2118 my $pool = extract_param
($param, 'pool');
2120 if (defined($pool)) {
2121 $rpcenv->check_pool_exist($pool);
2124 my $snapname = extract_param
($param, 'snapname');
2126 my $storage = extract_param
($param, 'storage');
2128 my $format = extract_param
($param, 'format');
2130 my $target = extract_param
($param, 'target');
2132 my $localnode = PVE
::INotify
::nodename
();
2134 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2136 PVE
::Cluster
::check_node_exists
($target) if $target;
2138 my $storecfg = PVE
::Storage
::config
();
2141 # check if storage is enabled on local node
2142 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2144 # check if storage is available on target node
2145 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2146 # clone only works if target storage is shared
2147 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2148 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2152 PVE
::Cluster
::check_cfs_quorum
();
2154 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2156 # exclusive lock if VM is running - else shared lock is enough;
2157 my $shared_lock = $running ?
0 : 1;
2161 # do all tests after lock
2162 # we also try to do all tests before we fork the worker
2164 my $conf = PVE
::QemuServer
::load_config
($vmid);
2166 PVE
::QemuServer
::check_lock
($conf);
2168 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2170 die "unexpected state change\n" if $verify_running != $running;
2172 die "snapshot '$snapname' does not exist\n"
2173 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2175 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2177 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2179 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2181 my $conffile = PVE
::QemuServer
::config_file
($newid);
2183 die "unable to create VM $newid: config file already exists\n"
2186 my $newconf = { lock => 'clone' };
2190 foreach my $opt (keys %$oldconf) {
2191 my $value = $oldconf->{$opt};
2193 # do not copy snapshot related info
2194 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2195 $opt eq 'vmstate' || $opt eq 'snapstate';
2197 # always change MAC! address
2198 if ($opt =~ m/^net(\d+)$/) {
2199 my $net = PVE
::QemuServer
::parse_net
($value);
2200 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2201 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2202 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2203 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2204 die "unable to parse drive options for '$opt'\n" if !$drive;
2205 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2206 $newconf->{$opt} = $value; # simply copy configuration
2208 if ($param->{full
}) {
2209 die "Full clone feature is not available"
2210 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2213 # not full means clone instead of copy
2214 die "Linked clone feature is not available"
2215 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2217 $drives->{$opt} = $drive;
2218 push @$vollist, $drive->{file
};
2221 # copy everything else
2222 $newconf->{$opt} = $value;
2226 # auto generate a new uuid
2227 my ($uuid, $uuid_str);
2228 UUID
::generate
($uuid);
2229 UUID
::unparse
($uuid, $uuid_str);
2230 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2231 $smbios1->{uuid
} = $uuid_str;
2232 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2234 delete $newconf->{template
};
2236 if ($param->{name
}) {
2237 $newconf->{name
} = $param->{name
};
2239 if ($oldconf->{name
}) {
2240 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2242 $newconf->{name
} = "Copy-of-VM-$vmid";
2246 if ($param->{description
}) {
2247 $newconf->{description
} = $param->{description
};
2250 # create empty/temp config - this fails if VM already exists on other node
2251 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2256 my $newvollist = [];
2259 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2261 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2263 foreach my $opt (keys %$drives) {
2264 my $drive = $drives->{$opt};
2266 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2267 $newid, $storage, $format, $drive->{full
}, $newvollist);
2269 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2271 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2274 delete $newconf->{lock};
2275 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2278 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2279 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2281 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2282 die "Failed to move config to node '$target' - rename failed: $!\n"
2283 if !rename($conffile, $newconffile);
2286 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2291 sleep 1; # some storage like rbd need to wait before release volume - really?
2293 foreach my $volid (@$newvollist) {
2294 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2297 die "clone failed: $err";
2303 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2306 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2307 # Aquire exclusive lock lock for $newid
2308 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2313 __PACKAGE__-
>register_method({
2314 name
=> 'move_vm_disk',
2315 path
=> '{vmid}/move_disk',
2319 description
=> "Move volume to different storage.",
2321 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2322 "and 'Datastore.AllocateSpace' permissions on the storage.",
2325 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2326 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2330 additionalProperties
=> 0,
2332 node
=> get_standard_option
('pve-node'),
2333 vmid
=> get_standard_option
('pve-vmid'),
2336 description
=> "The disk you want to move.",
2337 enum
=> [ PVE
::QemuServer
::disknames
() ],
2339 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2342 description
=> "Target Format.",
2343 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2348 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2354 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2362 description
=> "the task ID.",
2367 my $rpcenv = PVE
::RPCEnvironment
::get
();
2369 my $authuser = $rpcenv->get_user();
2371 my $node = extract_param
($param, 'node');
2373 my $vmid = extract_param
($param, 'vmid');
2375 my $digest = extract_param
($param, 'digest');
2377 my $disk = extract_param
($param, 'disk');
2379 my $storeid = extract_param
($param, 'storage');
2381 my $format = extract_param
($param, 'format');
2383 my $storecfg = PVE
::Storage
::config
();
2385 my $updatefn = sub {
2387 my $conf = PVE
::QemuServer
::load_config
($vmid);
2389 die "checksum missmatch (file change by other user?)\n"
2390 if $digest && $digest ne $conf->{digest
};
2392 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2394 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2396 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2398 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2401 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2402 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2406 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2407 (!$format || !$oldfmt || $oldfmt eq $format);
2409 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2411 my $running = PVE
::QemuServer
::check_running
($vmid);
2413 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2417 my $newvollist = [];
2420 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2422 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2423 $vmid, $storeid, $format, 1, $newvollist);
2425 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2427 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2429 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2432 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2433 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2440 foreach my $volid (@$newvollist) {
2441 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2444 die "storage migration failed: $err";
2447 if ($param->{delete}) {
2448 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2449 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2450 if ($used_paths->{$path}){
2451 warn "volume $old_volid have snapshots. Can't delete it\n";
2452 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2453 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2455 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2461 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2464 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2467 __PACKAGE__-
>register_method({
2468 name
=> 'migrate_vm',
2469 path
=> '{vmid}/migrate',
2473 description
=> "Migrate virtual machine. Creates a new migration task.",
2475 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2478 additionalProperties
=> 0,
2480 node
=> get_standard_option
('pve-node'),
2481 vmid
=> get_standard_option
('pve-vmid'),
2482 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2485 description
=> "Use online/live migration.",
2490 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2497 description
=> "the task ID.",
2502 my $rpcenv = PVE
::RPCEnvironment
::get
();
2504 my $authuser = $rpcenv->get_user();
2506 my $target = extract_param
($param, 'target');
2508 my $localnode = PVE
::INotify
::nodename
();
2509 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2511 PVE
::Cluster
::check_cfs_quorum
();
2513 PVE
::Cluster
::check_node_exists
($target);
2515 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2517 my $vmid = extract_param
($param, 'vmid');
2519 raise_param_exc
({ force
=> "Only root may use this option." })
2520 if $param->{force
} && $authuser ne 'root@pam';
2523 my $conf = PVE
::QemuServer
::load_config
($vmid);
2525 # try to detect errors early
2527 PVE
::QemuServer
::check_lock
($conf);
2529 if (PVE
::QemuServer
::check_running
($vmid)) {
2530 die "cant migrate running VM without --online\n"
2531 if !$param->{online
};
2534 my $storecfg = PVE
::Storage
::config
();
2535 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2537 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2542 my $service = "pvevm:$vmid";
2544 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2546 print "Executing HA migrate for VM $vmid to node $target\n";
2548 PVE
::Tools
::run_command
($cmd);
2553 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2560 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2563 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2568 __PACKAGE__-
>register_method({
2570 path
=> '{vmid}/monitor',
2574 description
=> "Execute Qemu monitor commands.",
2576 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2579 additionalProperties
=> 0,
2581 node
=> get_standard_option
('pve-node'),
2582 vmid
=> get_standard_option
('pve-vmid'),
2585 description
=> "The monitor command.",
2589 returns
=> { type
=> 'string'},
2593 my $vmid = $param->{vmid
};
2595 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2599 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2601 $res = "ERROR: $@" if $@;
2606 __PACKAGE__-
>register_method({
2607 name
=> 'resize_vm',
2608 path
=> '{vmid}/resize',
2612 description
=> "Extend volume size.",
2614 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2617 additionalProperties
=> 0,
2619 node
=> get_standard_option
('pve-node'),
2620 vmid
=> get_standard_option
('pve-vmid'),
2621 skiplock
=> get_standard_option
('skiplock'),
2624 description
=> "The disk you want to resize.",
2625 enum
=> [PVE
::QemuServer
::disknames
()],
2629 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2630 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.",
2634 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2640 returns
=> { type
=> 'null'},
2644 my $rpcenv = PVE
::RPCEnvironment
::get
();
2646 my $authuser = $rpcenv->get_user();
2648 my $node = extract_param
($param, 'node');
2650 my $vmid = extract_param
($param, 'vmid');
2652 my $digest = extract_param
($param, 'digest');
2654 my $disk = extract_param
($param, 'disk');
2656 my $sizestr = extract_param
($param, 'size');
2658 my $skiplock = extract_param
($param, 'skiplock');
2659 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2660 if $skiplock && $authuser ne 'root@pam';
2662 my $storecfg = PVE
::Storage
::config
();
2664 my $updatefn = sub {
2666 my $conf = PVE
::QemuServer
::load_config
($vmid);
2668 die "checksum missmatch (file change by other user?)\n"
2669 if $digest && $digest ne $conf->{digest
};
2670 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2672 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2674 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2676 my $volid = $drive->{file
};
2678 die "disk '$disk' has no associated volume\n" if !$volid;
2680 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2682 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2684 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2686 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2688 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2689 my ($ext, $newsize, $unit) = ($1, $2, $4);
2692 $newsize = $newsize * 1024;
2693 } elsif ($unit eq 'M') {
2694 $newsize = $newsize * 1024 * 1024;
2695 } elsif ($unit eq 'G') {
2696 $newsize = $newsize * 1024 * 1024 * 1024;
2697 } elsif ($unit eq 'T') {
2698 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2701 $newsize += $size if $ext;
2702 $newsize = int($newsize);
2704 die "unable to skrink disk size\n" if $newsize < $size;
2706 return if $size == $newsize;
2708 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2710 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2712 $drive->{size
} = $newsize;
2713 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2715 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2718 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2722 __PACKAGE__-
>register_method({
2723 name
=> 'snapshot_list',
2724 path
=> '{vmid}/snapshot',
2726 description
=> "List all snapshots.",
2728 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2731 protected
=> 1, # qemu pid files are only readable by root
2733 additionalProperties
=> 0,
2735 vmid
=> get_standard_option
('pve-vmid'),
2736 node
=> get_standard_option
('pve-node'),
2745 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2750 my $vmid = $param->{vmid
};
2752 my $conf = PVE
::QemuServer
::load_config
($vmid);
2753 my $snaphash = $conf->{snapshots
} || {};
2757 foreach my $name (keys %$snaphash) {
2758 my $d = $snaphash->{$name};
2761 snaptime
=> $d->{snaptime
} || 0,
2762 vmstate
=> $d->{vmstate
} ?
1 : 0,
2763 description
=> $d->{description
} || '',
2765 $item->{parent
} = $d->{parent
} if $d->{parent
};
2766 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2770 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2771 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2772 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2774 push @$res, $current;
2779 __PACKAGE__-
>register_method({
2781 path
=> '{vmid}/snapshot',
2785 description
=> "Snapshot a VM.",
2787 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2790 additionalProperties
=> 0,
2792 node
=> get_standard_option
('pve-node'),
2793 vmid
=> get_standard_option
('pve-vmid'),
2794 snapname
=> get_standard_option
('pve-snapshot-name'),
2798 description
=> "Save the vmstate",
2803 description
=> "A textual description or comment.",
2809 description
=> "the task ID.",
2814 my $rpcenv = PVE
::RPCEnvironment
::get
();
2816 my $authuser = $rpcenv->get_user();
2818 my $node = extract_param
($param, 'node');
2820 my $vmid = extract_param
($param, 'vmid');
2822 my $snapname = extract_param
($param, 'snapname');
2824 die "unable to use snapshot name 'current' (reserved name)\n"
2825 if $snapname eq 'current';
2828 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2829 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2830 $param->{description
});
2833 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2836 __PACKAGE__-
>register_method({
2837 name
=> 'snapshot_cmd_idx',
2838 path
=> '{vmid}/snapshot/{snapname}',
2845 additionalProperties
=> 0,
2847 vmid
=> get_standard_option
('pve-vmid'),
2848 node
=> get_standard_option
('pve-node'),
2849 snapname
=> get_standard_option
('pve-snapshot-name'),
2858 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2865 push @$res, { cmd
=> 'rollback' };
2866 push @$res, { cmd
=> 'config' };
2871 __PACKAGE__-
>register_method({
2872 name
=> 'update_snapshot_config',
2873 path
=> '{vmid}/snapshot/{snapname}/config',
2877 description
=> "Update snapshot metadata.",
2879 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2882 additionalProperties
=> 0,
2884 node
=> get_standard_option
('pve-node'),
2885 vmid
=> get_standard_option
('pve-vmid'),
2886 snapname
=> get_standard_option
('pve-snapshot-name'),
2890 description
=> "A textual description or comment.",
2894 returns
=> { type
=> 'null' },
2898 my $rpcenv = PVE
::RPCEnvironment
::get
();
2900 my $authuser = $rpcenv->get_user();
2902 my $vmid = extract_param
($param, 'vmid');
2904 my $snapname = extract_param
($param, 'snapname');
2906 return undef if !defined($param->{description
});
2908 my $updatefn = sub {
2910 my $conf = PVE
::QemuServer
::load_config
($vmid);
2912 PVE
::QemuServer
::check_lock
($conf);
2914 my $snap = $conf->{snapshots
}->{$snapname};
2916 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2918 $snap->{description
} = $param->{description
} if defined($param->{description
});
2920 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2923 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2928 __PACKAGE__-
>register_method({
2929 name
=> 'get_snapshot_config',
2930 path
=> '{vmid}/snapshot/{snapname}/config',
2933 description
=> "Get snapshot configuration",
2935 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2938 additionalProperties
=> 0,
2940 node
=> get_standard_option
('pve-node'),
2941 vmid
=> get_standard_option
('pve-vmid'),
2942 snapname
=> get_standard_option
('pve-snapshot-name'),
2945 returns
=> { type
=> "object" },
2949 my $rpcenv = PVE
::RPCEnvironment
::get
();
2951 my $authuser = $rpcenv->get_user();
2953 my $vmid = extract_param
($param, 'vmid');
2955 my $snapname = extract_param
($param, 'snapname');
2957 my $conf = PVE
::QemuServer
::load_config
($vmid);
2959 my $snap = $conf->{snapshots
}->{$snapname};
2961 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2966 __PACKAGE__-
>register_method({
2968 path
=> '{vmid}/snapshot/{snapname}/rollback',
2972 description
=> "Rollback VM state to specified snapshot.",
2974 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2977 additionalProperties
=> 0,
2979 node
=> get_standard_option
('pve-node'),
2980 vmid
=> get_standard_option
('pve-vmid'),
2981 snapname
=> get_standard_option
('pve-snapshot-name'),
2986 description
=> "the task ID.",
2991 my $rpcenv = PVE
::RPCEnvironment
::get
();
2993 my $authuser = $rpcenv->get_user();
2995 my $node = extract_param
($param, 'node');
2997 my $vmid = extract_param
($param, 'vmid');
2999 my $snapname = extract_param
($param, 'snapname');
3002 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3003 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3006 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3009 __PACKAGE__-
>register_method({
3010 name
=> 'delsnapshot',
3011 path
=> '{vmid}/snapshot/{snapname}',
3015 description
=> "Delete a VM snapshot.",
3017 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3020 additionalProperties
=> 0,
3022 node
=> get_standard_option
('pve-node'),
3023 vmid
=> get_standard_option
('pve-vmid'),
3024 snapname
=> get_standard_option
('pve-snapshot-name'),
3028 description
=> "For removal from config file, even if removing disk snapshots fails.",
3034 description
=> "the task ID.",
3039 my $rpcenv = PVE
::RPCEnvironment
::get
();
3041 my $authuser = $rpcenv->get_user();
3043 my $node = extract_param
($param, 'node');
3045 my $vmid = extract_param
($param, 'vmid');
3047 my $snapname = extract_param
($param, 'snapname');
3050 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3051 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3054 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3057 __PACKAGE__-
>register_method({
3059 path
=> '{vmid}/template',
3063 description
=> "Create a Template.",
3065 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3066 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3069 additionalProperties
=> 0,
3071 node
=> get_standard_option
('pve-node'),
3072 vmid
=> get_standard_option
('pve-vmid'),
3076 description
=> "If you want to convert only 1 disk to base image.",
3077 enum
=> [PVE
::QemuServer
::disknames
()],
3082 returns
=> { type
=> 'null'},
3086 my $rpcenv = PVE
::RPCEnvironment
::get
();
3088 my $authuser = $rpcenv->get_user();
3090 my $node = extract_param
($param, 'node');
3092 my $vmid = extract_param
($param, 'vmid');
3094 my $disk = extract_param
($param, 'disk');
3096 my $updatefn = sub {
3098 my $conf = PVE
::QemuServer
::load_config
($vmid);
3100 PVE
::QemuServer
::check_lock
($conf);
3102 die "unable to create template, because VM contains snapshots\n"
3103 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3105 die "you can't convert a template to a template\n"
3106 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3108 die "you can't convert a VM to template if VM is running\n"
3109 if PVE
::QemuServer
::check_running
($vmid);
3112 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3115 $conf->{template
} = 1;
3116 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3118 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3121 PVE
::QemuServer
::lock_config
($vmid, $updatefn);