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 $check_vm_modify_config_perm = sub {
192 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
194 return 1 if $authuser eq 'root@pam';
196 foreach my $opt (@$key_list) {
197 # disk checks need to be done somewhere else
198 next if PVE
::QemuServer
::valid_drivename
($opt);
200 if ($opt eq 'sockets' || $opt eq 'cores' ||
201 $opt eq 'cpu' || $opt eq 'smp' ||
202 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
203 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
204 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
205 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
206 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
207 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
208 } elsif ($opt eq 'args' || $opt eq 'lock') {
209 die "only root can set '$opt' config\n";
210 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
211 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
212 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
213 } elsif ($opt =~ m/^net\d+$/) {
214 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
216 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
223 __PACKAGE__-
>register_method({
227 description
=> "Virtual machine index (per node).",
229 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
233 protected
=> 1, # qemu pid files are only readable by root
235 additionalProperties
=> 0,
237 node
=> get_standard_option
('pve-node'),
246 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
251 my $rpcenv = PVE
::RPCEnvironment
::get
();
252 my $authuser = $rpcenv->get_user();
254 my $vmstatus = PVE
::QemuServer
::vmstatus
();
257 foreach my $vmid (keys %$vmstatus) {
258 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
260 my $data = $vmstatus->{$vmid};
261 $data->{vmid
} = int($vmid);
270 __PACKAGE__-
>register_method({
274 description
=> "Create or restore a virtual machine.",
276 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
277 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
278 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
279 user
=> 'all', # check inside
284 additionalProperties
=> 0,
285 properties
=> PVE
::QemuServer
::json_config_properties
(
287 node
=> get_standard_option
('pve-node'),
288 vmid
=> get_standard_option
('pve-vmid'),
290 description
=> "The backup file.",
295 storage
=> get_standard_option
('pve-storage-id', {
296 description
=> "Default storage.",
302 description
=> "Allow to overwrite existing VM.",
303 requires
=> 'archive',
308 description
=> "Assign a unique random ethernet address.",
309 requires
=> 'archive',
313 type
=> 'string', format
=> 'pve-poolid',
314 description
=> "Add the VM to the specified pool.",
324 my $rpcenv = PVE
::RPCEnvironment
::get
();
326 my $authuser = $rpcenv->get_user();
328 my $node = extract_param
($param, 'node');
330 my $vmid = extract_param
($param, 'vmid');
332 my $archive = extract_param
($param, 'archive');
334 my $storage = extract_param
($param, 'storage');
336 my $force = extract_param
($param, 'force');
338 my $unique = extract_param
($param, 'unique');
340 my $pool = extract_param
($param, 'pool');
342 my $filename = PVE
::QemuServer
::config_file
($vmid);
344 my $storecfg = PVE
::Storage
::config
();
346 PVE
::Cluster
::check_cfs_quorum
();
348 if (defined($pool)) {
349 $rpcenv->check_pool_exist($pool);
352 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
353 if defined($storage);
355 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
357 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
359 } elsif ($archive && $force && (-f
$filename) &&
360 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
361 # OK: user has VM.Backup permissions, and want to restore an existing VM
367 &$resolve_cdrom_alias($param);
369 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
371 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
373 foreach my $opt (keys %$param) {
374 if (PVE
::QemuServer
::valid_drivename
($opt)) {
375 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
376 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
378 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
379 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
383 PVE
::QemuServer
::add_random_macs
($param);
385 my $keystr = join(' ', keys %$param);
386 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
388 if ($archive eq '-') {
389 die "pipe requires cli environment\n"
390 if $rpcenv->{type
} ne 'cli';
392 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
393 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
397 my $restorefn = sub {
399 # fixme: this test does not work if VM exists on other node!
401 die "unable to restore vm $vmid: config file already exists\n"
404 die "unable to restore vm $vmid: vm is running\n"
405 if PVE
::QemuServer
::check_running
($vmid);
409 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
412 unique
=> $unique });
414 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
417 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
423 die "unable to create vm $vmid: config file already exists\n"
434 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
436 # try to be smart about bootdisk
437 my @disks = PVE
::QemuServer
::disknames
();
439 foreach my $ds (reverse @disks) {
440 next if !$conf->{$ds};
441 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
442 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
446 if (!$conf->{bootdisk
} && $firstdisk) {
447 $conf->{bootdisk
} = $firstdisk;
450 # auto generate uuid if user did not specify smbios1 option
451 if (!$conf->{smbios1
}) {
452 my ($uuid, $uuid_str);
453 UUID
::generate
($uuid);
454 UUID
::unparse
($uuid, $uuid_str);
455 $conf->{smbios1
} = "uuid=$uuid_str";
458 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
464 foreach my $volid (@$vollist) {
465 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
468 die "create failed - $err";
471 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
474 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
477 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
480 __PACKAGE__-
>register_method({
485 description
=> "Directory index",
490 additionalProperties
=> 0,
492 node
=> get_standard_option
('pve-node'),
493 vmid
=> get_standard_option
('pve-vmid'),
501 subdir
=> { type
=> 'string' },
504 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
510 { subdir
=> 'config' },
511 { subdir
=> 'status' },
512 { subdir
=> 'unlink' },
513 { subdir
=> 'vncproxy' },
514 { subdir
=> 'migrate' },
515 { subdir
=> 'resize' },
516 { subdir
=> 'move' },
518 { subdir
=> 'rrddata' },
519 { subdir
=> 'monitor' },
520 { subdir
=> 'snapshot' },
521 { subdir
=> 'spiceproxy' },
522 { subdir
=> 'sendkey' },
523 { subdir
=> 'firewall' },
529 __PACKAGE__-
>register_method ({
530 subclass
=> "PVE::API2::Firewall::VM",
531 path
=> '{vmid}/firewall',
534 __PACKAGE__-
>register_method({
536 path
=> '{vmid}/rrd',
538 protected
=> 1, # fixme: can we avoid that?
540 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
542 description
=> "Read VM RRD statistics (returns PNG)",
544 additionalProperties
=> 0,
546 node
=> get_standard_option
('pve-node'),
547 vmid
=> get_standard_option
('pve-vmid'),
549 description
=> "Specify the time frame you are interested in.",
551 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
554 description
=> "The list of datasources you want to display.",
555 type
=> 'string', format
=> 'pve-configid-list',
558 description
=> "The RRD consolidation function",
560 enum
=> [ 'AVERAGE', 'MAX' ],
568 filename
=> { type
=> 'string' },
574 return PVE
::Cluster
::create_rrd_graph
(
575 "pve2-vm/$param->{vmid}", $param->{timeframe
},
576 $param->{ds
}, $param->{cf
});
580 __PACKAGE__-
>register_method({
582 path
=> '{vmid}/rrddata',
584 protected
=> 1, # fixme: can we avoid that?
586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
588 description
=> "Read VM RRD statistics",
590 additionalProperties
=> 0,
592 node
=> get_standard_option
('pve-node'),
593 vmid
=> get_standard_option
('pve-vmid'),
595 description
=> "Specify the time frame you are interested in.",
597 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
600 description
=> "The RRD consolidation function",
602 enum
=> [ 'AVERAGE', 'MAX' ],
617 return PVE
::Cluster
::create_rrd_data
(
618 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
622 __PACKAGE__-
>register_method({
624 path
=> '{vmid}/config',
627 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
629 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
632 additionalProperties
=> 0,
634 node
=> get_standard_option
('pve-node'),
635 vmid
=> get_standard_option
('pve-vmid'),
643 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
650 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
652 delete $conf->{snapshots
};
653 delete $conf->{pending
};
658 __PACKAGE__-
>register_method({
659 name
=> 'vm_pending',
660 path
=> '{vmid}/pending',
663 description
=> "Get virtual machine configuration, including pending changes.",
665 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
668 additionalProperties
=> 0,
670 node
=> get_standard_option
('pve-node'),
671 vmid
=> get_standard_option
('pve-vmid'),
680 description
=> "Configuration option name.",
684 description
=> "Current value.",
689 description
=> "Pending value.",
694 description
=> "Indicated a pending delete request.",
704 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
706 my $pending_delete_hash = {};
707 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
708 $pending_delete_hash->{$opt} = 1;
713 foreach my $opt (keys $conf) {
714 next if ref($conf->{$opt});
715 my $item = { key
=> $opt };
716 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
717 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
718 $item->{delete} = 1 if $pending_delete_hash->{$opt};
722 foreach my $opt (keys $conf->{pending
}) {
723 next if $opt eq 'delete';
724 next if ref($conf->{pending
}->{$opt}); # just to be sure
725 next if $conf->{$opt};
726 my $item = { key
=> $opt };
727 $item->{pending
} = $conf->{pending
}->{$opt};
731 foreach my $opt (PVE
::Tools
::split_list
($conf->{pending
}->{delete})) {
732 next if $conf->{pending
}->{$opt}; # just to be sure
733 next if $conf->{$opt};
734 my $item = { key
=> $opt, delete => 1};
741 my $delete_drive = sub {
742 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
744 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
745 my $volid = $drive->{file
};
747 if (PVE
::QemuServer
::vm_is_volid_owner
($storecfg, $vmid, $volid)) {
748 if ($force || $key =~ m/^unused/) {
750 # check if the disk is really unused
751 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
752 my $path = PVE
::Storage
::path
($storecfg, $volid);
754 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
755 if $used_paths->{$path};
757 PVE
::Storage
::vdisk_free
($storecfg, $volid);
761 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
766 delete $conf->{$key};
769 my $vmconfig_delete_option = sub {
770 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
772 return if !defined($conf->{$opt});
774 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
777 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
779 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
780 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
781 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
785 my $unplugwarning = "";
786 if ($conf->{ostype
} && $conf->{ostype
} eq 'l26') {
787 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
788 } elsif ($conf->{ostype
} && $conf->{ostype
} eq 'l24') {
789 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
790 } elsif (!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')) {
791 $unplugwarning = "<br>verify that your guest support acpi hotplug";
794 if ($opt eq 'tablet') {
795 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
797 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
801 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
802 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
804 delete $conf->{$opt};
807 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
810 # POST/PUT {vmid}/config implementation
812 # The original API used PUT (idempotent) an we assumed that all operations
813 # are fast. But it turned out that almost any configuration change can
814 # involve hot-plug actions, or disk alloc/free. Such actions can take long
815 # time to complete and have side effects (not idempotent).
817 # The new implementation uses POST and forks a worker process. We added
818 # a new option 'background_delay'. If specified we wait up to
819 # 'background_delay' second for the worker task to complete. It returns null
820 # if the task is finished within that time, else we return the UPID.
822 my $update_vm_api = sub {
823 my ($param, $sync) = @_;
825 my $rpcenv = PVE
::RPCEnvironment
::get
();
827 my $authuser = $rpcenv->get_user();
829 my $node = extract_param
($param, 'node');
831 my $vmid = extract_param
($param, 'vmid');
833 my $digest = extract_param
($param, 'digest');
835 my $background_delay = extract_param
($param, 'background_delay');
837 my @paramarr = (); # used for log message
838 foreach my $key (keys %$param) {
839 push @paramarr, "-$key", $param->{$key};
842 my $skiplock = extract_param
($param, 'skiplock');
843 raise_param_exc
({ skiplock
=> "Only root may use this option." })
844 if $skiplock && $authuser ne 'root@pam';
846 my $delete_str = extract_param
($param, 'delete');
848 my $force = extract_param
($param, 'force');
850 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
852 my $storecfg = PVE
::Storage
::config
();
854 my $defaults = PVE
::QemuServer
::load_defaults
();
856 &$resolve_cdrom_alias($param);
858 # now try to verify all parameters
861 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
862 $opt = 'ide2' if $opt eq 'cdrom';
863 raise_param_exc
({ delete => "you can't use '-$opt' and " .
864 "-delete $opt' at the same time" })
865 if defined($param->{$opt});
867 if (!PVE
::QemuServer
::option_exists
($opt)) {
868 raise_param_exc
({ delete => "unknown option '$opt'" });
874 foreach my $opt (keys %$param) {
875 if (PVE
::QemuServer
::valid_drivename
($opt)) {
877 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
878 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
879 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
880 } elsif ($opt =~ m/^net(\d+)$/) {
882 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
883 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
887 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
889 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
891 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
895 my $conf = PVE
::QemuServer
::load_config
($vmid);
897 die "checksum missmatch (file change by other user?)\n"
898 if $digest && $digest ne $conf->{digest
};
900 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
902 if ($param->{memory
} || defined($param->{balloon
})) {
903 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
904 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
906 die "balloon value too large (must be smaller than assigned memory)\n"
907 if $balloon && $balloon > $maxmem;
910 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
914 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
916 # write updates to pending section
918 my $modified = {}; # record what $option we modify
920 foreach my $opt (@delete) {
921 $modified->{$opt} = 1;
922 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
923 if ($opt =~ m/^unused/) {
924 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
925 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
926 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
927 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
928 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
929 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
931 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
932 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
933 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
934 if defined($conf->{pending
}->{$opt});
935 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
936 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
938 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt);
939 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
943 foreach my $opt (keys %$param) { # add/change
944 $modified->{$opt} = 1;
945 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
946 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
948 if (PVE
::QemuServer
::valid_drivename
($opt)) {
949 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
950 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
951 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
953 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
955 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
956 if defined($conf->{pending
}->{$opt});
958 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
960 $conf->{pending
}->{$opt} = $param->{$opt};
962 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
963 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
966 # remove pending changes when nothing changed
967 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
968 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
969 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
971 return if !scalar(keys %{$conf->{pending
}});
973 my $running = PVE
::QemuServer
::check_running
($vmid);
975 # apply pending changes
977 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
981 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
982 raise_param_exc
($errors) if scalar(keys %$errors);
984 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
986 return; # TODO: remove old code below
988 foreach my $opt (keys %$param) { # add/change
990 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
992 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
994 if (PVE
::QemuServer
::valid_drivename
($opt)) {
996 #&$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
997 # $opt, $param->{$opt}, $force);
999 } elsif ($opt =~ m/^net(\d+)$/) { #nics
1001 # &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
1002 # $opt, $param->{$opt});
1006 if($opt eq 'tablet' && $param->{$opt} == 1){
1007 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1008 } elsif($opt eq 'tablet' && $param->{$opt} == 0){
1009 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1012 if($opt eq 'cores' && $conf->{maxcpus
}){
1013 PVE
::QemuServer
::qemu_cpu_hotplug
($vmid, $conf, $param->{$opt});
1016 $conf->{$opt} = $param->{$opt};
1017 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1021 # allow manual ballooning if shares is set to zero
1022 if ($running && defined($param->{balloon
}) &&
1023 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1024 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1025 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1033 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1035 if ($background_delay) {
1037 # Note: It would be better to do that in the Event based HTTPServer
1038 # to avoid blocking call to sleep.
1040 my $end_time = time() + $background_delay;
1042 my $task = PVE
::Tools
::upid_decode
($upid);
1045 while (time() < $end_time) {
1046 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1048 sleep(1); # this gets interrupted when child process ends
1052 my $status = PVE
::Tools
::upid_read_status
($upid);
1053 return undef if $status eq 'OK';
1062 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1065 my $vm_config_perm_list = [
1070 'VM.Config.Network',
1072 'VM.Config.Options',
1075 __PACKAGE__-
>register_method({
1076 name
=> 'update_vm_async',
1077 path
=> '{vmid}/config',
1081 description
=> "Set virtual machine options (asynchrounous API).",
1083 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1086 additionalProperties
=> 0,
1087 properties
=> PVE
::QemuServer
::json_config_properties
(
1089 node
=> get_standard_option
('pve-node'),
1090 vmid
=> get_standard_option
('pve-vmid'),
1091 skiplock
=> get_standard_option
('skiplock'),
1093 type
=> 'string', format
=> 'pve-configid-list',
1094 description
=> "A list of settings you want to delete.",
1099 description
=> $opt_force_description,
1101 requires
=> 'delete',
1105 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1109 background_delay
=> {
1111 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1122 code
=> $update_vm_api,
1125 __PACKAGE__-
>register_method({
1126 name
=> 'update_vm',
1127 path
=> '{vmid}/config',
1131 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1133 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1136 additionalProperties
=> 0,
1137 properties
=> PVE
::QemuServer
::json_config_properties
(
1139 node
=> get_standard_option
('pve-node'),
1140 vmid
=> get_standard_option
('pve-vmid'),
1141 skiplock
=> get_standard_option
('skiplock'),
1143 type
=> 'string', format
=> 'pve-configid-list',
1144 description
=> "A list of settings you want to delete.",
1149 description
=> $opt_force_description,
1151 requires
=> 'delete',
1155 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1161 returns
=> { type
=> 'null' },
1164 &$update_vm_api($param, 1);
1170 __PACKAGE__-
>register_method({
1171 name
=> 'destroy_vm',
1176 description
=> "Destroy the vm (also delete all used/owned volumes).",
1178 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1181 additionalProperties
=> 0,
1183 node
=> get_standard_option
('pve-node'),
1184 vmid
=> get_standard_option
('pve-vmid'),
1185 skiplock
=> get_standard_option
('skiplock'),
1194 my $rpcenv = PVE
::RPCEnvironment
::get
();
1196 my $authuser = $rpcenv->get_user();
1198 my $vmid = $param->{vmid
};
1200 my $skiplock = $param->{skiplock
};
1201 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1202 if $skiplock && $authuser ne 'root@pam';
1205 my $conf = PVE
::QemuServer
::load_config
($vmid);
1207 my $storecfg = PVE
::Storage
::config
();
1209 my $delVMfromPoolFn = sub {
1210 my $usercfg = cfs_read_file
("user.cfg");
1211 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1212 if (my $data = $usercfg->{pools
}->{$pool}) {
1213 delete $data->{vms
}->{$vmid};
1214 delete $usercfg->{vms
}->{$vmid};
1215 cfs_write_file
("user.cfg", $usercfg);
1223 syslog
('info', "destroy VM $vmid: $upid\n");
1225 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1227 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1230 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1233 __PACKAGE__-
>register_method({
1235 path
=> '{vmid}/unlink',
1239 description
=> "Unlink/delete disk images.",
1241 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1244 additionalProperties
=> 0,
1246 node
=> get_standard_option
('pve-node'),
1247 vmid
=> get_standard_option
('pve-vmid'),
1249 type
=> 'string', format
=> 'pve-configid-list',
1250 description
=> "A list of disk IDs you want to delete.",
1254 description
=> $opt_force_description,
1259 returns
=> { type
=> 'null'},
1263 $param->{delete} = extract_param
($param, 'idlist');
1265 __PACKAGE__-
>update_vm($param);
1272 __PACKAGE__-
>register_method({
1274 path
=> '{vmid}/vncproxy',
1278 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1280 description
=> "Creates a TCP VNC proxy connections.",
1282 additionalProperties
=> 0,
1284 node
=> get_standard_option
('pve-node'),
1285 vmid
=> get_standard_option
('pve-vmid'),
1289 description
=> "starts websockify instead of vncproxy",
1294 additionalProperties
=> 0,
1296 user
=> { type
=> 'string' },
1297 ticket
=> { type
=> 'string' },
1298 cert
=> { type
=> 'string' },
1299 port
=> { type
=> 'integer' },
1300 upid
=> { type
=> 'string' },
1306 my $rpcenv = PVE
::RPCEnvironment
::get
();
1308 my $authuser = $rpcenv->get_user();
1310 my $vmid = $param->{vmid
};
1311 my $node = $param->{node
};
1312 my $websocket = $param->{websocket
};
1314 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1316 my $authpath = "/vms/$vmid";
1318 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1320 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1323 my $port = PVE
::Tools
::next_vnc_port
();
1328 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1329 $remip = PVE
::Cluster
::remote_node_ip
($node);
1330 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1331 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1339 syslog
('info', "starting vnc proxy $upid\n");
1343 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1345 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1347 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1348 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1349 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1350 '-timeout', $timeout, '-authpath', $authpath,
1351 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1354 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1356 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1358 my $qmstr = join(' ', @$qmcmd);
1360 # also redirect stderr (else we get RFB protocol errors)
1361 $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1364 PVE
::Tools
::run_command
($cmd);
1369 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1371 PVE
::Tools
::wait_for_vnc_port
($port);
1382 __PACKAGE__-
>register_method({
1383 name
=> 'vncwebsocket',
1384 path
=> '{vmid}/vncwebsocket',
1387 description
=> "You also need to pass a valid ticket (vncticket).",
1388 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1390 description
=> "Opens a weksocket for VNC traffic.",
1392 additionalProperties
=> 0,
1394 node
=> get_standard_option
('pve-node'),
1395 vmid
=> get_standard_option
('pve-vmid'),
1397 description
=> "Ticket from previous call to vncproxy.",
1402 description
=> "Port number returned by previous vncproxy call.",
1412 port
=> { type
=> 'string' },
1418 my $rpcenv = PVE
::RPCEnvironment
::get
();
1420 my $authuser = $rpcenv->get_user();
1422 my $vmid = $param->{vmid
};
1423 my $node = $param->{node
};
1425 my $authpath = "/vms/$vmid";
1427 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1429 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1431 # Note: VNC ports are acessible from outside, so we do not gain any
1432 # security if we verify that $param->{port} belongs to VM $vmid. This
1433 # check is done by verifying the VNC ticket (inside VNC protocol).
1435 my $port = $param->{port
};
1437 return { port
=> $port };
1440 __PACKAGE__-
>register_method({
1441 name
=> 'spiceproxy',
1442 path
=> '{vmid}/spiceproxy',
1447 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1449 description
=> "Returns a SPICE configuration to connect to the VM.",
1451 additionalProperties
=> 0,
1453 node
=> get_standard_option
('pve-node'),
1454 vmid
=> get_standard_option
('pve-vmid'),
1455 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1458 returns
=> get_standard_option
('remote-viewer-config'),
1462 my $rpcenv = PVE
::RPCEnvironment
::get
();
1464 my $authuser = $rpcenv->get_user();
1466 my $vmid = $param->{vmid
};
1467 my $node = $param->{node
};
1468 my $proxy = $param->{proxy
};
1470 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1471 my $title = "VM $vmid - $conf->{'name'}",
1473 my $port = PVE
::QemuServer
::spice_port
($vmid);
1475 my ($ticket, undef, $remote_viewer_config) =
1476 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1478 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1479 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1481 return $remote_viewer_config;
1484 __PACKAGE__-
>register_method({
1486 path
=> '{vmid}/status',
1489 description
=> "Directory index",
1494 additionalProperties
=> 0,
1496 node
=> get_standard_option
('pve-node'),
1497 vmid
=> get_standard_option
('pve-vmid'),
1505 subdir
=> { type
=> 'string' },
1508 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1514 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1517 { subdir
=> 'current' },
1518 { subdir
=> 'start' },
1519 { subdir
=> 'stop' },
1525 my $vm_is_ha_managed = sub {
1528 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1529 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1535 __PACKAGE__-
>register_method({
1536 name
=> 'vm_status',
1537 path
=> '{vmid}/status/current',
1540 protected
=> 1, # qemu pid files are only readable by root
1541 description
=> "Get virtual machine status.",
1543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1546 additionalProperties
=> 0,
1548 node
=> get_standard_option
('pve-node'),
1549 vmid
=> get_standard_option
('pve-vmid'),
1552 returns
=> { type
=> 'object' },
1557 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1559 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1560 my $status = $vmstatus->{$param->{vmid
}};
1562 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1564 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1569 __PACKAGE__-
>register_method({
1571 path
=> '{vmid}/status/start',
1575 description
=> "Start virtual machine.",
1577 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1580 additionalProperties
=> 0,
1582 node
=> get_standard_option
('pve-node'),
1583 vmid
=> get_standard_option
('pve-vmid'),
1584 skiplock
=> get_standard_option
('skiplock'),
1585 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1586 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1587 machine
=> get_standard_option
('pve-qm-machine'),
1596 my $rpcenv = PVE
::RPCEnvironment
::get
();
1598 my $authuser = $rpcenv->get_user();
1600 my $node = extract_param
($param, 'node');
1602 my $vmid = extract_param
($param, 'vmid');
1604 my $machine = extract_param
($param, 'machine');
1606 my $stateuri = extract_param
($param, 'stateuri');
1607 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1608 if $stateuri && $authuser ne 'root@pam';
1610 my $skiplock = extract_param
($param, 'skiplock');
1611 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1612 if $skiplock && $authuser ne 'root@pam';
1614 my $migratedfrom = extract_param
($param, 'migratedfrom');
1615 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1616 if $migratedfrom && $authuser ne 'root@pam';
1618 # read spice ticket from STDIN
1620 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1621 if (defined(my $line = <>)) {
1623 $spice_ticket = $line;
1627 my $storecfg = PVE
::Storage
::config
();
1629 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1630 $rpcenv->{type
} ne 'ha') {
1635 my $service = "pvevm:$vmid";
1637 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1639 print "Executing HA start for VM $vmid\n";
1641 PVE
::Tools
::run_command
($cmd);
1646 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1653 syslog
('info', "start VM $vmid: $upid\n");
1655 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1656 $machine, $spice_ticket);
1661 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1665 __PACKAGE__-
>register_method({
1667 path
=> '{vmid}/status/stop',
1671 description
=> "Stop virtual machine.",
1673 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1676 additionalProperties
=> 0,
1678 node
=> get_standard_option
('pve-node'),
1679 vmid
=> get_standard_option
('pve-vmid'),
1680 skiplock
=> get_standard_option
('skiplock'),
1681 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1683 description
=> "Wait maximal timeout seconds.",
1689 description
=> "Do not decativate storage volumes.",
1702 my $rpcenv = PVE
::RPCEnvironment
::get
();
1704 my $authuser = $rpcenv->get_user();
1706 my $node = extract_param
($param, 'node');
1708 my $vmid = extract_param
($param, 'vmid');
1710 my $skiplock = extract_param
($param, 'skiplock');
1711 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1712 if $skiplock && $authuser ne 'root@pam';
1714 my $keepActive = extract_param
($param, 'keepActive');
1715 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1716 if $keepActive && $authuser ne 'root@pam';
1718 my $migratedfrom = extract_param
($param, 'migratedfrom');
1719 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1720 if $migratedfrom && $authuser ne 'root@pam';
1723 my $storecfg = PVE
::Storage
::config
();
1725 if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1730 my $service = "pvevm:$vmid";
1732 my $cmd = ['clusvcadm', '-d', $service];
1734 print "Executing HA stop for VM $vmid\n";
1736 PVE
::Tools
::run_command
($cmd);
1741 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1747 syslog
('info', "stop VM $vmid: $upid\n");
1749 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1750 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1755 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1759 __PACKAGE__-
>register_method({
1761 path
=> '{vmid}/status/reset',
1765 description
=> "Reset virtual machine.",
1767 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1770 additionalProperties
=> 0,
1772 node
=> get_standard_option
('pve-node'),
1773 vmid
=> get_standard_option
('pve-vmid'),
1774 skiplock
=> get_standard_option
('skiplock'),
1783 my $rpcenv = PVE
::RPCEnvironment
::get
();
1785 my $authuser = $rpcenv->get_user();
1787 my $node = extract_param
($param, 'node');
1789 my $vmid = extract_param
($param, 'vmid');
1791 my $skiplock = extract_param
($param, 'skiplock');
1792 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1793 if $skiplock && $authuser ne 'root@pam';
1795 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1800 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1805 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1808 __PACKAGE__-
>register_method({
1809 name
=> 'vm_shutdown',
1810 path
=> '{vmid}/status/shutdown',
1814 description
=> "Shutdown virtual machine.",
1816 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1819 additionalProperties
=> 0,
1821 node
=> get_standard_option
('pve-node'),
1822 vmid
=> get_standard_option
('pve-vmid'),
1823 skiplock
=> get_standard_option
('skiplock'),
1825 description
=> "Wait maximal timeout seconds.",
1831 description
=> "Make sure the VM stops.",
1837 description
=> "Do not decativate storage volumes.",
1850 my $rpcenv = PVE
::RPCEnvironment
::get
();
1852 my $authuser = $rpcenv->get_user();
1854 my $node = extract_param
($param, 'node');
1856 my $vmid = extract_param
($param, 'vmid');
1858 my $skiplock = extract_param
($param, 'skiplock');
1859 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1860 if $skiplock && $authuser ne 'root@pam';
1862 my $keepActive = extract_param
($param, 'keepActive');
1863 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1864 if $keepActive && $authuser ne 'root@pam';
1866 my $storecfg = PVE
::Storage
::config
();
1871 syslog
('info', "shutdown VM $vmid: $upid\n");
1873 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1874 1, $param->{forceStop
}, $keepActive);
1879 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1882 __PACKAGE__-
>register_method({
1883 name
=> 'vm_suspend',
1884 path
=> '{vmid}/status/suspend',
1888 description
=> "Suspend virtual machine.",
1890 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1893 additionalProperties
=> 0,
1895 node
=> get_standard_option
('pve-node'),
1896 vmid
=> get_standard_option
('pve-vmid'),
1897 skiplock
=> get_standard_option
('skiplock'),
1906 my $rpcenv = PVE
::RPCEnvironment
::get
();
1908 my $authuser = $rpcenv->get_user();
1910 my $node = extract_param
($param, 'node');
1912 my $vmid = extract_param
($param, 'vmid');
1914 my $skiplock = extract_param
($param, 'skiplock');
1915 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1916 if $skiplock && $authuser ne 'root@pam';
1918 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1923 syslog
('info', "suspend VM $vmid: $upid\n");
1925 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1930 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1933 __PACKAGE__-
>register_method({
1934 name
=> 'vm_resume',
1935 path
=> '{vmid}/status/resume',
1939 description
=> "Resume virtual machine.",
1941 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1944 additionalProperties
=> 0,
1946 node
=> get_standard_option
('pve-node'),
1947 vmid
=> get_standard_option
('pve-vmid'),
1948 skiplock
=> get_standard_option
('skiplock'),
1957 my $rpcenv = PVE
::RPCEnvironment
::get
();
1959 my $authuser = $rpcenv->get_user();
1961 my $node = extract_param
($param, 'node');
1963 my $vmid = extract_param
($param, 'vmid');
1965 my $skiplock = extract_param
($param, 'skiplock');
1966 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1967 if $skiplock && $authuser ne 'root@pam';
1969 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1974 syslog
('info', "resume VM $vmid: $upid\n");
1976 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1981 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1984 __PACKAGE__-
>register_method({
1985 name
=> 'vm_sendkey',
1986 path
=> '{vmid}/sendkey',
1990 description
=> "Send key event to virtual machine.",
1992 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1995 additionalProperties
=> 0,
1997 node
=> get_standard_option
('pve-node'),
1998 vmid
=> get_standard_option
('pve-vmid'),
1999 skiplock
=> get_standard_option
('skiplock'),
2001 description
=> "The key (qemu monitor encoding).",
2006 returns
=> { type
=> 'null'},
2010 my $rpcenv = PVE
::RPCEnvironment
::get
();
2012 my $authuser = $rpcenv->get_user();
2014 my $node = extract_param
($param, 'node');
2016 my $vmid = extract_param
($param, 'vmid');
2018 my $skiplock = extract_param
($param, 'skiplock');
2019 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2020 if $skiplock && $authuser ne 'root@pam';
2022 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2027 __PACKAGE__-
>register_method({
2028 name
=> 'vm_feature',
2029 path
=> '{vmid}/feature',
2033 description
=> "Check if feature for virtual machine is available.",
2035 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2038 additionalProperties
=> 0,
2040 node
=> get_standard_option
('pve-node'),
2041 vmid
=> get_standard_option
('pve-vmid'),
2043 description
=> "Feature to check.",
2045 enum
=> [ 'snapshot', 'clone', 'copy' ],
2047 snapname
=> get_standard_option
('pve-snapshot-name', {
2055 hasFeature
=> { type
=> 'boolean' },
2058 items
=> { type
=> 'string' },
2065 my $node = extract_param
($param, 'node');
2067 my $vmid = extract_param
($param, 'vmid');
2069 my $snapname = extract_param
($param, 'snapname');
2071 my $feature = extract_param
($param, 'feature');
2073 my $running = PVE
::QemuServer
::check_running
($vmid);
2075 my $conf = PVE
::QemuServer
::load_config
($vmid);
2078 my $snap = $conf->{snapshots
}->{$snapname};
2079 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2082 my $storecfg = PVE
::Storage
::config
();
2084 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2085 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2088 hasFeature
=> $hasFeature,
2089 nodes
=> [ keys %$nodelist ],
2093 __PACKAGE__-
>register_method({
2095 path
=> '{vmid}/clone',
2099 description
=> "Create a copy of virtual machine/template.",
2101 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2102 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2103 "'Datastore.AllocateSpace' on any used storage.",
2106 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2108 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2109 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2114 additionalProperties
=> 0,
2116 node
=> get_standard_option
('pve-node'),
2117 vmid
=> get_standard_option
('pve-vmid'),
2118 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2121 type
=> 'string', format
=> 'dns-name',
2122 description
=> "Set a name for the new VM.",
2127 description
=> "Description for the new VM.",
2131 type
=> 'string', format
=> 'pve-poolid',
2132 description
=> "Add the new VM to the specified pool.",
2134 snapname
=> get_standard_option
('pve-snapshot-name', {
2137 storage
=> get_standard_option
('pve-storage-id', {
2138 description
=> "Target storage for full clone.",
2143 description
=> "Target format for file storage.",
2147 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2152 description
=> "Create a full copy of all disk. This is always done when " .
2153 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2156 target
=> get_standard_option
('pve-node', {
2157 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2168 my $rpcenv = PVE
::RPCEnvironment
::get
();
2170 my $authuser = $rpcenv->get_user();
2172 my $node = extract_param
($param, 'node');
2174 my $vmid = extract_param
($param, 'vmid');
2176 my $newid = extract_param
($param, 'newid');
2178 my $pool = extract_param
($param, 'pool');
2180 if (defined($pool)) {
2181 $rpcenv->check_pool_exist($pool);
2184 my $snapname = extract_param
($param, 'snapname');
2186 my $storage = extract_param
($param, 'storage');
2188 my $format = extract_param
($param, 'format');
2190 my $target = extract_param
($param, 'target');
2192 my $localnode = PVE
::INotify
::nodename
();
2194 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2196 PVE
::Cluster
::check_node_exists
($target) if $target;
2198 my $storecfg = PVE
::Storage
::config
();
2201 # check if storage is enabled on local node
2202 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2204 # check if storage is available on target node
2205 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2206 # clone only works if target storage is shared
2207 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2208 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2212 PVE
::Cluster
::check_cfs_quorum
();
2214 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2216 # exclusive lock if VM is running - else shared lock is enough;
2217 my $shared_lock = $running ?
0 : 1;
2221 # do all tests after lock
2222 # we also try to do all tests before we fork the worker
2224 my $conf = PVE
::QemuServer
::load_config
($vmid);
2226 PVE
::QemuServer
::check_lock
($conf);
2228 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2230 die "unexpected state change\n" if $verify_running != $running;
2232 die "snapshot '$snapname' does not exist\n"
2233 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2235 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2237 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2239 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2241 my $conffile = PVE
::QemuServer
::config_file
($newid);
2243 die "unable to create VM $newid: config file already exists\n"
2246 my $newconf = { lock => 'clone' };
2250 foreach my $opt (keys %$oldconf) {
2251 my $value = $oldconf->{$opt};
2253 # do not copy snapshot related info
2254 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2255 $opt eq 'vmstate' || $opt eq 'snapstate';
2257 # always change MAC! address
2258 if ($opt =~ m/^net(\d+)$/) {
2259 my $net = PVE
::QemuServer
::parse_net
($value);
2260 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2261 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2262 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2263 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2264 die "unable to parse drive options for '$opt'\n" if !$drive;
2265 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2266 $newconf->{$opt} = $value; # simply copy configuration
2268 if ($param->{full
}) {
2269 die "Full clone feature is not available"
2270 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2273 # not full means clone instead of copy
2274 die "Linked clone feature is not available"
2275 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2277 $drives->{$opt} = $drive;
2278 push @$vollist, $drive->{file
};
2281 # copy everything else
2282 $newconf->{$opt} = $value;
2286 # auto generate a new uuid
2287 my ($uuid, $uuid_str);
2288 UUID
::generate
($uuid);
2289 UUID
::unparse
($uuid, $uuid_str);
2290 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2291 $smbios1->{uuid
} = $uuid_str;
2292 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2294 delete $newconf->{template
};
2296 if ($param->{name
}) {
2297 $newconf->{name
} = $param->{name
};
2299 if ($oldconf->{name
}) {
2300 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2302 $newconf->{name
} = "Copy-of-VM-$vmid";
2306 if ($param->{description
}) {
2307 $newconf->{description
} = $param->{description
};
2310 # create empty/temp config - this fails if VM already exists on other node
2311 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2316 my $newvollist = [];
2319 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2321 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2323 foreach my $opt (keys %$drives) {
2324 my $drive = $drives->{$opt};
2326 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2327 $newid, $storage, $format, $drive->{full
}, $newvollist);
2329 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2331 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2334 delete $newconf->{lock};
2335 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2338 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2339 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2341 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2342 die "Failed to move config to node '$target' - rename failed: $!\n"
2343 if !rename($conffile, $newconffile);
2346 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2351 sleep 1; # some storage like rbd need to wait before release volume - really?
2353 foreach my $volid (@$newvollist) {
2354 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2357 die "clone failed: $err";
2363 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2366 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2367 # Aquire exclusive lock lock for $newid
2368 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2373 __PACKAGE__-
>register_method({
2374 name
=> 'move_vm_disk',
2375 path
=> '{vmid}/move_disk',
2379 description
=> "Move volume to different storage.",
2381 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2382 "and 'Datastore.AllocateSpace' permissions on the storage.",
2385 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2386 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2390 additionalProperties
=> 0,
2392 node
=> get_standard_option
('pve-node'),
2393 vmid
=> get_standard_option
('pve-vmid'),
2396 description
=> "The disk you want to move.",
2397 enum
=> [ PVE
::QemuServer
::disknames
() ],
2399 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2402 description
=> "Target Format.",
2403 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2408 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2414 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2422 description
=> "the task ID.",
2427 my $rpcenv = PVE
::RPCEnvironment
::get
();
2429 my $authuser = $rpcenv->get_user();
2431 my $node = extract_param
($param, 'node');
2433 my $vmid = extract_param
($param, 'vmid');
2435 my $digest = extract_param
($param, 'digest');
2437 my $disk = extract_param
($param, 'disk');
2439 my $storeid = extract_param
($param, 'storage');
2441 my $format = extract_param
($param, 'format');
2443 my $storecfg = PVE
::Storage
::config
();
2445 my $updatefn = sub {
2447 my $conf = PVE
::QemuServer
::load_config
($vmid);
2449 die "checksum missmatch (file change by other user?)\n"
2450 if $digest && $digest ne $conf->{digest
};
2452 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2454 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2456 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2458 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2461 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2462 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2466 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2467 (!$format || !$oldfmt || $oldfmt eq $format);
2469 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2471 my $running = PVE
::QemuServer
::check_running
($vmid);
2473 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2477 my $newvollist = [];
2480 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2482 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2483 $vmid, $storeid, $format, 1, $newvollist);
2485 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2487 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2489 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2492 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2493 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2500 foreach my $volid (@$newvollist) {
2501 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2504 die "storage migration failed: $err";
2507 if ($param->{delete}) {
2508 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2509 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2510 if ($used_paths->{$path}){
2511 warn "volume $old_volid have snapshots. Can't delete it\n";
2512 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2513 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2515 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2521 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2524 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2527 __PACKAGE__-
>register_method({
2528 name
=> 'migrate_vm',
2529 path
=> '{vmid}/migrate',
2533 description
=> "Migrate virtual machine. Creates a new migration task.",
2535 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2538 additionalProperties
=> 0,
2540 node
=> get_standard_option
('pve-node'),
2541 vmid
=> get_standard_option
('pve-vmid'),
2542 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2545 description
=> "Use online/live migration.",
2550 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2557 description
=> "the task ID.",
2562 my $rpcenv = PVE
::RPCEnvironment
::get
();
2564 my $authuser = $rpcenv->get_user();
2566 my $target = extract_param
($param, 'target');
2568 my $localnode = PVE
::INotify
::nodename
();
2569 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2571 PVE
::Cluster
::check_cfs_quorum
();
2573 PVE
::Cluster
::check_node_exists
($target);
2575 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2577 my $vmid = extract_param
($param, 'vmid');
2579 raise_param_exc
({ force
=> "Only root may use this option." })
2580 if $param->{force
} && $authuser ne 'root@pam';
2583 my $conf = PVE
::QemuServer
::load_config
($vmid);
2585 # try to detect errors early
2587 PVE
::QemuServer
::check_lock
($conf);
2589 if (PVE
::QemuServer
::check_running
($vmid)) {
2590 die "cant migrate running VM without --online\n"
2591 if !$param->{online
};
2594 my $storecfg = PVE
::Storage
::config
();
2595 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2597 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2602 my $service = "pvevm:$vmid";
2604 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2606 print "Executing HA migrate for VM $vmid to node $target\n";
2608 PVE
::Tools
::run_command
($cmd);
2613 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2620 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2623 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2628 __PACKAGE__-
>register_method({
2630 path
=> '{vmid}/monitor',
2634 description
=> "Execute Qemu monitor commands.",
2636 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2639 additionalProperties
=> 0,
2641 node
=> get_standard_option
('pve-node'),
2642 vmid
=> get_standard_option
('pve-vmid'),
2645 description
=> "The monitor command.",
2649 returns
=> { type
=> 'string'},
2653 my $vmid = $param->{vmid
};
2655 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2659 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2661 $res = "ERROR: $@" if $@;
2666 __PACKAGE__-
>register_method({
2667 name
=> 'resize_vm',
2668 path
=> '{vmid}/resize',
2672 description
=> "Extend volume size.",
2674 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2677 additionalProperties
=> 0,
2679 node
=> get_standard_option
('pve-node'),
2680 vmid
=> get_standard_option
('pve-vmid'),
2681 skiplock
=> get_standard_option
('skiplock'),
2684 description
=> "The disk you want to resize.",
2685 enum
=> [PVE
::QemuServer
::disknames
()],
2689 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2690 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.",
2694 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2700 returns
=> { type
=> 'null'},
2704 my $rpcenv = PVE
::RPCEnvironment
::get
();
2706 my $authuser = $rpcenv->get_user();
2708 my $node = extract_param
($param, 'node');
2710 my $vmid = extract_param
($param, 'vmid');
2712 my $digest = extract_param
($param, 'digest');
2714 my $disk = extract_param
($param, 'disk');
2716 my $sizestr = extract_param
($param, 'size');
2718 my $skiplock = extract_param
($param, 'skiplock');
2719 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2720 if $skiplock && $authuser ne 'root@pam';
2722 my $storecfg = PVE
::Storage
::config
();
2724 my $updatefn = sub {
2726 my $conf = PVE
::QemuServer
::load_config
($vmid);
2728 die "checksum missmatch (file change by other user?)\n"
2729 if $digest && $digest ne $conf->{digest
};
2730 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2732 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2734 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2736 my $volid = $drive->{file
};
2738 die "disk '$disk' has no associated volume\n" if !$volid;
2740 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2742 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2744 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2746 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2748 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2749 my ($ext, $newsize, $unit) = ($1, $2, $4);
2752 $newsize = $newsize * 1024;
2753 } elsif ($unit eq 'M') {
2754 $newsize = $newsize * 1024 * 1024;
2755 } elsif ($unit eq 'G') {
2756 $newsize = $newsize * 1024 * 1024 * 1024;
2757 } elsif ($unit eq 'T') {
2758 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2761 $newsize += $size if $ext;
2762 $newsize = int($newsize);
2764 die "unable to skrink disk size\n" if $newsize < $size;
2766 return if $size == $newsize;
2768 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2770 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2772 $drive->{size
} = $newsize;
2773 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2775 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2778 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2782 __PACKAGE__-
>register_method({
2783 name
=> 'snapshot_list',
2784 path
=> '{vmid}/snapshot',
2786 description
=> "List all snapshots.",
2788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2791 protected
=> 1, # qemu pid files are only readable by root
2793 additionalProperties
=> 0,
2795 vmid
=> get_standard_option
('pve-vmid'),
2796 node
=> get_standard_option
('pve-node'),
2805 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2810 my $vmid = $param->{vmid
};
2812 my $conf = PVE
::QemuServer
::load_config
($vmid);
2813 my $snaphash = $conf->{snapshots
} || {};
2817 foreach my $name (keys %$snaphash) {
2818 my $d = $snaphash->{$name};
2821 snaptime
=> $d->{snaptime
} || 0,
2822 vmstate
=> $d->{vmstate
} ?
1 : 0,
2823 description
=> $d->{description
} || '',
2825 $item->{parent
} = $d->{parent
} if $d->{parent
};
2826 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2830 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2831 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2832 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2834 push @$res, $current;
2839 __PACKAGE__-
>register_method({
2841 path
=> '{vmid}/snapshot',
2845 description
=> "Snapshot a VM.",
2847 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2850 additionalProperties
=> 0,
2852 node
=> get_standard_option
('pve-node'),
2853 vmid
=> get_standard_option
('pve-vmid'),
2854 snapname
=> get_standard_option
('pve-snapshot-name'),
2858 description
=> "Save the vmstate",
2863 description
=> "A textual description or comment.",
2869 description
=> "the task ID.",
2874 my $rpcenv = PVE
::RPCEnvironment
::get
();
2876 my $authuser = $rpcenv->get_user();
2878 my $node = extract_param
($param, 'node');
2880 my $vmid = extract_param
($param, 'vmid');
2882 my $snapname = extract_param
($param, 'snapname');
2884 die "unable to use snapshot name 'current' (reserved name)\n"
2885 if $snapname eq 'current';
2888 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2889 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2890 $param->{description
});
2893 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2896 __PACKAGE__-
>register_method({
2897 name
=> 'snapshot_cmd_idx',
2898 path
=> '{vmid}/snapshot/{snapname}',
2905 additionalProperties
=> 0,
2907 vmid
=> get_standard_option
('pve-vmid'),
2908 node
=> get_standard_option
('pve-node'),
2909 snapname
=> get_standard_option
('pve-snapshot-name'),
2918 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2925 push @$res, { cmd
=> 'rollback' };
2926 push @$res, { cmd
=> 'config' };
2931 __PACKAGE__-
>register_method({
2932 name
=> 'update_snapshot_config',
2933 path
=> '{vmid}/snapshot/{snapname}/config',
2937 description
=> "Update snapshot metadata.",
2939 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2942 additionalProperties
=> 0,
2944 node
=> get_standard_option
('pve-node'),
2945 vmid
=> get_standard_option
('pve-vmid'),
2946 snapname
=> get_standard_option
('pve-snapshot-name'),
2950 description
=> "A textual description or comment.",
2954 returns
=> { type
=> 'null' },
2958 my $rpcenv = PVE
::RPCEnvironment
::get
();
2960 my $authuser = $rpcenv->get_user();
2962 my $vmid = extract_param
($param, 'vmid');
2964 my $snapname = extract_param
($param, 'snapname');
2966 return undef if !defined($param->{description
});
2968 my $updatefn = sub {
2970 my $conf = PVE
::QemuServer
::load_config
($vmid);
2972 PVE
::QemuServer
::check_lock
($conf);
2974 my $snap = $conf->{snapshots
}->{$snapname};
2976 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2978 $snap->{description
} = $param->{description
} if defined($param->{description
});
2980 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2983 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2988 __PACKAGE__-
>register_method({
2989 name
=> 'get_snapshot_config',
2990 path
=> '{vmid}/snapshot/{snapname}/config',
2993 description
=> "Get snapshot configuration",
2995 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2998 additionalProperties
=> 0,
3000 node
=> get_standard_option
('pve-node'),
3001 vmid
=> get_standard_option
('pve-vmid'),
3002 snapname
=> get_standard_option
('pve-snapshot-name'),
3005 returns
=> { type
=> "object" },
3009 my $rpcenv = PVE
::RPCEnvironment
::get
();
3011 my $authuser = $rpcenv->get_user();
3013 my $vmid = extract_param
($param, 'vmid');
3015 my $snapname = extract_param
($param, 'snapname');
3017 my $conf = PVE
::QemuServer
::load_config
($vmid);
3019 my $snap = $conf->{snapshots
}->{$snapname};
3021 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3026 __PACKAGE__-
>register_method({
3028 path
=> '{vmid}/snapshot/{snapname}/rollback',
3032 description
=> "Rollback VM state to specified snapshot.",
3034 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3037 additionalProperties
=> 0,
3039 node
=> get_standard_option
('pve-node'),
3040 vmid
=> get_standard_option
('pve-vmid'),
3041 snapname
=> get_standard_option
('pve-snapshot-name'),
3046 description
=> "the task ID.",
3051 my $rpcenv = PVE
::RPCEnvironment
::get
();
3053 my $authuser = $rpcenv->get_user();
3055 my $node = extract_param
($param, 'node');
3057 my $vmid = extract_param
($param, 'vmid');
3059 my $snapname = extract_param
($param, 'snapname');
3062 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3063 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3066 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3069 __PACKAGE__-
>register_method({
3070 name
=> 'delsnapshot',
3071 path
=> '{vmid}/snapshot/{snapname}',
3075 description
=> "Delete a VM snapshot.",
3077 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3080 additionalProperties
=> 0,
3082 node
=> get_standard_option
('pve-node'),
3083 vmid
=> get_standard_option
('pve-vmid'),
3084 snapname
=> get_standard_option
('pve-snapshot-name'),
3088 description
=> "For removal from config file, even if removing disk snapshots fails.",
3094 description
=> "the task ID.",
3099 my $rpcenv = PVE
::RPCEnvironment
::get
();
3101 my $authuser = $rpcenv->get_user();
3103 my $node = extract_param
($param, 'node');
3105 my $vmid = extract_param
($param, 'vmid');
3107 my $snapname = extract_param
($param, 'snapname');
3110 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3111 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3114 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3117 __PACKAGE__-
>register_method({
3119 path
=> '{vmid}/template',
3123 description
=> "Create a Template.",
3125 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3126 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3129 additionalProperties
=> 0,
3131 node
=> get_standard_option
('pve-node'),
3132 vmid
=> get_standard_option
('pve-vmid'),
3136 description
=> "If you want to convert only 1 disk to base image.",
3137 enum
=> [PVE
::QemuServer
::disknames
()],
3142 returns
=> { type
=> 'null'},
3146 my $rpcenv = PVE
::RPCEnvironment
::get
();
3148 my $authuser = $rpcenv->get_user();
3150 my $node = extract_param
($param, 'node');
3152 my $vmid = extract_param
($param, 'vmid');
3154 my $disk = extract_param
($param, 'disk');
3156 my $updatefn = sub {
3158 my $conf = PVE
::QemuServer
::load_config
($vmid);
3160 PVE
::QemuServer
::check_lock
($conf);
3162 die "unable to create template, because VM contains snapshots\n"
3163 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3165 die "you can't convert a template to a template\n"
3166 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3168 die "you can't convert a VM to template if VM is running\n"
3169 if PVE
::QemuServer
::check_running
($vmid);
3172 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3175 $conf->{template
} = 1;
3176 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3178 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3181 PVE
::QemuServer
::lock_config
($vmid, $updatefn);