1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::ReplicationConfig
;
22 use PVE
::RPCEnvironment
;
23 use PVE
::AccessControl
;
27 use PVE
::API2
::Firewall
::VM
;
30 if (!$ENV{PVE_GENERATING_DOCS
}) {
31 require PVE
::HA
::Env
::PVE2
;
32 import PVE
::HA
::Env
::PVE2
;
33 require PVE
::HA
::Config
;
34 import PVE
::HA
::Config
;
38 use Data
::Dumper
; # fixme: remove
40 use base
qw(PVE::RESTHandler);
42 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.";
44 my $resolve_cdrom_alias = sub {
47 if (my $value = $param->{cdrom
}) {
48 $value .= ",media=cdrom" if $value !~ m/media=/;
49 $param->{ide2
} = $value;
50 delete $param->{cdrom
};
54 my $check_storage_access = sub {
55 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
57 PVE
::QemuServer
::foreach_drive
($settings, sub {
58 my ($ds, $drive) = @_;
60 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
62 my $volid = $drive->{file
};
64 if (!$volid || $volid eq 'none') {
66 } elsif ($isCDROM && ($volid eq 'cdrom')) {
67 $rpcenv->check($authuser, "/", ['Sys.Console']);
68 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
69 my ($storeid, $size) = ($2 || $default_storage, $3);
70 die "no storage ID specified (and no default storage)\n" if !$storeid;
71 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
73 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
78 my $check_storage_access_clone = sub {
79 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
83 PVE
::QemuServer
::foreach_drive
($conf, sub {
84 my ($ds, $drive) = @_;
86 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
88 my $volid = $drive->{file
};
90 return if !$volid || $volid eq 'none';
93 if ($volid eq 'cdrom') {
94 $rpcenv->check($authuser, "/", ['Sys.Console']);
96 # we simply allow access
97 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
98 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
99 $sharedvm = 0 if !$scfg->{shared
};
103 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
104 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
105 $sharedvm = 0 if !$scfg->{shared
};
107 $sid = $storage if $storage;
108 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
115 # Note: $pool is only needed when creating a VM, because pool permissions
116 # are automatically inherited if VM already exists inside a pool.
117 my $create_disks = sub {
118 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
125 my ($ds, $disk) = @_;
127 my $volid = $disk->{file
};
129 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
130 delete $disk->{size
};
131 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
132 } elsif ($volid =~ m!^(([^/:\s]+):)?(\d+(\.\d+)?)$!) {
133 my ($storeid, $size) = ($2 || $default_storage, $3);
134 die "no storage ID specified (and no default storage)\n" if !$storeid;
135 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
136 my $fmt = $disk->{format
} || $defformat;
139 if ($ds eq 'efidisk0') {
141 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
142 die "uefi vars image not found\n" if ! -f
$ovmfvars;
143 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
145 $disk->{file
} = $volid;
146 $disk->{size
} = 128*1024;
147 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
148 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
149 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
150 my $path = PVE
::Storage
::path
($storecfg, $volid);
151 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
152 push @$efidiskcmd, $ovmfvars;
153 push @$efidiskcmd, $path;
155 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
157 eval { PVE
::Tools
::run_command
($efidiskcmd); };
159 die "Copying of EFI Vars image failed: $err" if $err;
161 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
162 $fmt, undef, $size*1024*1024);
163 $disk->{file
} = $volid;
164 $disk->{size
} = $size*1024*1024*1024;
166 push @$vollist, $volid;
167 delete $disk->{format
}; # no longer needed
168 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
171 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
173 my $volid_is_new = 1;
176 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
177 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
182 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
184 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
186 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
188 die "volume $volid does not exists\n" if !$size;
190 $disk->{size
} = $size;
193 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
197 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
199 # free allocated images on error
201 syslog
('err', "VM $vmid creating disks failed");
202 foreach my $volid (@$vollist) {
203 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
209 # modify vm config if everything went well
210 foreach my $ds (keys %$res) {
211 $conf->{$ds} = $res->{$ds};
228 my $memoryoptions = {
234 my $hwtypeoptions = {
246 my $generaloptions = {
253 'migrate_downtime' => 1,
254 'migrate_speed' => 1,
266 my $vmpoweroptions = {
275 my $check_vm_modify_config_perm = sub {
276 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
278 return 1 if $authuser eq 'root@pam';
280 foreach my $opt (@$key_list) {
281 # disk checks need to be done somewhere else
282 next if PVE
::QemuServer
::is_valid_drivename
($opt);
283 next if $opt eq 'cdrom';
284 next if $opt =~ m/^unused\d+$/;
286 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
287 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
288 } elsif ($memoryoptions->{$opt}) {
289 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
290 } elsif ($hwtypeoptions->{$opt}) {
291 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
292 } elsif ($generaloptions->{$opt}) {
293 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
294 # special case for startup since it changes host behaviour
295 if ($opt eq 'startup') {
296 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
298 } elsif ($vmpoweroptions->{$opt}) {
299 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
300 } elsif ($diskoptions->{$opt}) {
301 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
302 } elsif ($opt =~ m/^net\d+$/) {
303 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
305 # catches usb\d+, hostpci\d+, args, lock, etc.
306 # new options will be checked here
307 die "only root can set '$opt' config\n";
314 __PACKAGE__-
>register_method({
318 description
=> "Virtual machine index (per node).",
320 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
324 protected
=> 1, # qemu pid files are only readable by root
326 additionalProperties
=> 0,
328 node
=> get_standard_option
('pve-node'),
332 description
=> "Determine the full status of active VMs.",
342 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
347 my $rpcenv = PVE
::RPCEnvironment
::get
();
348 my $authuser = $rpcenv->get_user();
350 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
353 foreach my $vmid (keys %$vmstatus) {
354 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
356 my $data = $vmstatus->{$vmid};
357 $data->{vmid
} = int($vmid);
366 __PACKAGE__-
>register_method({
370 description
=> "Create or restore a virtual machine.",
372 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
373 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
374 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
375 user
=> 'all', # check inside
380 additionalProperties
=> 0,
381 properties
=> PVE
::QemuServer
::json_config_properties
(
383 node
=> get_standard_option
('pve-node'),
384 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
386 description
=> "The backup file.",
390 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
392 storage
=> get_standard_option
('pve-storage-id', {
393 description
=> "Default storage.",
395 completion
=> \
&PVE
::QemuServer
::complete_storage
,
400 description
=> "Allow to overwrite existing VM.",
401 requires
=> 'archive',
406 description
=> "Assign a unique random ethernet address.",
407 requires
=> 'archive',
411 type
=> 'string', format
=> 'pve-poolid',
412 description
=> "Add the VM to the specified pool.",
422 my $rpcenv = PVE
::RPCEnvironment
::get
();
424 my $authuser = $rpcenv->get_user();
426 my $node = extract_param
($param, 'node');
428 my $vmid = extract_param
($param, 'vmid');
430 my $archive = extract_param
($param, 'archive');
432 my $storage = extract_param
($param, 'storage');
434 my $force = extract_param
($param, 'force');
436 my $unique = extract_param
($param, 'unique');
438 my $pool = extract_param
($param, 'pool');
440 my $filename = PVE
::QemuConfig-
>config_file($vmid);
442 my $storecfg = PVE
::Storage
::config
();
444 PVE
::Cluster
::check_cfs_quorum
();
446 if (defined($pool)) {
447 $rpcenv->check_pool_exist($pool);
450 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
451 if defined($storage);
453 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
455 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
457 } elsif ($archive && $force && (-f
$filename) &&
458 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
459 # OK: user has VM.Backup permissions, and want to restore an existing VM
465 &$resolve_cdrom_alias($param);
467 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
469 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
471 foreach my $opt (keys %$param) {
472 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
473 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
474 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
476 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
477 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
481 PVE
::QemuServer
::add_random_macs
($param);
483 my $keystr = join(' ', keys %$param);
484 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
486 if ($archive eq '-') {
487 die "pipe requires cli environment\n"
488 if $rpcenv->{type
} ne 'cli';
490 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
491 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
495 my $restorefn = sub {
496 my $vmlist = PVE
::Cluster
::get_vmlist
();
497 if ($vmlist->{ids
}->{$vmid}) {
498 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
499 if ($current_node eq $node) {
500 my $conf = PVE
::QemuConfig-
>load_config($vmid);
502 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
504 die "unable to restore vm $vmid - config file already exists\n"
507 die "unable to restore vm $vmid - vm is running\n"
508 if PVE
::QemuServer
::check_running
($vmid);
510 die "unable to restore vm $vmid - vm is a template\n"
511 if PVE
::QemuConfig-
>is_template($conf);
514 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
519 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
522 unique
=> $unique });
524 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
527 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
533 PVE
::Cluster
::check_vmid_unused
($vmid);
543 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
545 # try to be smart about bootdisk
546 my @disks = PVE
::QemuServer
::valid_drive_names
();
548 foreach my $ds (reverse @disks) {
549 next if !$conf->{$ds};
550 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
551 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
555 if (!$conf->{bootdisk
} && $firstdisk) {
556 $conf->{bootdisk
} = $firstdisk;
559 # auto generate uuid if user did not specify smbios1 option
560 if (!$conf->{smbios1
}) {
561 my ($uuid, $uuid_str);
562 UUID
::generate
($uuid);
563 UUID
::unparse
($uuid, $uuid_str);
564 $conf->{smbios1
} = "uuid=$uuid_str";
567 PVE
::QemuConfig-
>write_config($vmid, $conf);
573 foreach my $volid (@$vollist) {
574 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
577 die "create failed - $err";
580 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
583 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
586 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
589 __PACKAGE__-
>register_method({
594 description
=> "Directory index",
599 additionalProperties
=> 0,
601 node
=> get_standard_option
('pve-node'),
602 vmid
=> get_standard_option
('pve-vmid'),
610 subdir
=> { type
=> 'string' },
613 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
619 { subdir
=> 'config' },
620 { subdir
=> 'pending' },
621 { subdir
=> 'status' },
622 { subdir
=> 'unlink' },
623 { subdir
=> 'vncproxy' },
624 { subdir
=> 'migrate' },
625 { subdir
=> 'resize' },
626 { subdir
=> 'move' },
628 { subdir
=> 'rrddata' },
629 { subdir
=> 'monitor' },
630 { subdir
=> 'agent' },
631 { subdir
=> 'snapshot' },
632 { subdir
=> 'spiceproxy' },
633 { subdir
=> 'sendkey' },
634 { subdir
=> 'firewall' },
640 __PACKAGE__-
>register_method ({
641 subclass
=> "PVE::API2::Firewall::VM",
642 path
=> '{vmid}/firewall',
645 __PACKAGE__-
>register_method({
647 path
=> '{vmid}/rrd',
649 protected
=> 1, # fixme: can we avoid that?
651 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
653 description
=> "Read VM RRD statistics (returns PNG)",
655 additionalProperties
=> 0,
657 node
=> get_standard_option
('pve-node'),
658 vmid
=> get_standard_option
('pve-vmid'),
660 description
=> "Specify the time frame you are interested in.",
662 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
665 description
=> "The list of datasources you want to display.",
666 type
=> 'string', format
=> 'pve-configid-list',
669 description
=> "The RRD consolidation function",
671 enum
=> [ 'AVERAGE', 'MAX' ],
679 filename
=> { type
=> 'string' },
685 return PVE
::Cluster
::create_rrd_graph
(
686 "pve2-vm/$param->{vmid}", $param->{timeframe
},
687 $param->{ds
}, $param->{cf
});
691 __PACKAGE__-
>register_method({
693 path
=> '{vmid}/rrddata',
695 protected
=> 1, # fixme: can we avoid that?
697 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
699 description
=> "Read VM RRD statistics",
701 additionalProperties
=> 0,
703 node
=> get_standard_option
('pve-node'),
704 vmid
=> get_standard_option
('pve-vmid'),
706 description
=> "Specify the time frame you are interested in.",
708 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
711 description
=> "The RRD consolidation function",
713 enum
=> [ 'AVERAGE', 'MAX' ],
728 return PVE
::Cluster
::create_rrd_data
(
729 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
733 __PACKAGE__-
>register_method({
735 path
=> '{vmid}/config',
738 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
743 additionalProperties
=> 0,
745 node
=> get_standard_option
('pve-node'),
746 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
748 description
=> "Get current values (instead of pending values).",
760 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
767 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
769 delete $conf->{snapshots
};
771 if (!$param->{current
}) {
772 foreach my $opt (keys %{$conf->{pending
}}) {
773 next if $opt eq 'delete';
774 my $value = $conf->{pending
}->{$opt};
775 next if ref($value); # just to be sure
776 $conf->{$opt} = $value;
778 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
779 foreach my $opt (keys %$pending_delete_hash) {
780 delete $conf->{$opt} if $conf->{$opt};
784 delete $conf->{pending
};
789 __PACKAGE__-
>register_method({
790 name
=> 'vm_pending',
791 path
=> '{vmid}/pending',
794 description
=> "Get virtual machine configuration, including pending changes.",
796 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
799 additionalProperties
=> 0,
801 node
=> get_standard_option
('pve-node'),
802 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
811 description
=> "Configuration option name.",
815 description
=> "Current value.",
820 description
=> "Pending value.",
825 description
=> "Indicates a pending delete request if present and not 0. " .
826 "The value 2 indicates a force-delete request.",
838 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
840 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
844 foreach my $opt (keys %$conf) {
845 next if ref($conf->{$opt});
846 my $item = { key
=> $opt };
847 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
848 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
849 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
853 foreach my $opt (keys %{$conf->{pending
}}) {
854 next if $opt eq 'delete';
855 next if ref($conf->{pending
}->{$opt}); # just to be sure
856 next if defined($conf->{$opt});
857 my $item = { key
=> $opt };
858 $item->{pending
} = $conf->{pending
}->{$opt};
862 while (my ($opt, $force) = each %$pending_delete_hash) {
863 next if $conf->{pending
}->{$opt}; # just to be sure
864 next if $conf->{$opt};
865 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
872 # POST/PUT {vmid}/config implementation
874 # The original API used PUT (idempotent) an we assumed that all operations
875 # are fast. But it turned out that almost any configuration change can
876 # involve hot-plug actions, or disk alloc/free. Such actions can take long
877 # time to complete and have side effects (not idempotent).
879 # The new implementation uses POST and forks a worker process. We added
880 # a new option 'background_delay'. If specified we wait up to
881 # 'background_delay' second for the worker task to complete. It returns null
882 # if the task is finished within that time, else we return the UPID.
884 my $update_vm_api = sub {
885 my ($param, $sync) = @_;
887 my $rpcenv = PVE
::RPCEnvironment
::get
();
889 my $authuser = $rpcenv->get_user();
891 my $node = extract_param
($param, 'node');
893 my $vmid = extract_param
($param, 'vmid');
895 my $digest = extract_param
($param, 'digest');
897 my $background_delay = extract_param
($param, 'background_delay');
899 my @paramarr = (); # used for log message
900 foreach my $key (keys %$param) {
901 push @paramarr, "-$key", $param->{$key};
904 my $skiplock = extract_param
($param, 'skiplock');
905 raise_param_exc
({ skiplock
=> "Only root may use this option." })
906 if $skiplock && $authuser ne 'root@pam';
908 my $delete_str = extract_param
($param, 'delete');
910 my $revert_str = extract_param
($param, 'revert');
912 my $force = extract_param
($param, 'force');
914 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
916 my $storecfg = PVE
::Storage
::config
();
918 my $defaults = PVE
::QemuServer
::load_defaults
();
920 &$resolve_cdrom_alias($param);
922 # now try to verify all parameters
925 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
926 if (!PVE
::QemuServer
::option_exists
($opt)) {
927 raise_param_exc
({ revert
=> "unknown option '$opt'" });
930 raise_param_exc
({ delete => "you can't use '-$opt' and " .
931 "-revert $opt' at the same time" })
932 if defined($param->{$opt});
938 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
939 $opt = 'ide2' if $opt eq 'cdrom';
941 raise_param_exc
({ delete => "you can't use '-$opt' and " .
942 "-delete $opt' at the same time" })
943 if defined($param->{$opt});
945 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
946 "-revert $opt' at the same time" })
949 if (!PVE
::QemuServer
::option_exists
($opt)) {
950 raise_param_exc
({ delete => "unknown option '$opt'" });
956 foreach my $opt (keys %$param) {
957 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
959 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
960 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
961 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
962 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
963 } elsif ($opt =~ m/^net(\d+)$/) {
965 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
966 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
970 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
972 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
974 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
978 my $conf = PVE
::QemuConfig-
>load_config($vmid);
980 die "checksum missmatch (file change by other user?)\n"
981 if $digest && $digest ne $conf->{digest
};
983 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
985 foreach my $opt (keys %$revert) {
986 if (defined($conf->{$opt})) {
987 $param->{$opt} = $conf->{$opt};
988 } elsif (defined($conf->{pending
}->{$opt})) {
993 if ($param->{memory
} || defined($param->{balloon
})) {
994 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
995 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
997 die "balloon value too large (must be smaller than assigned memory)\n"
998 if $balloon && $balloon > $maxmem;
1001 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1005 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1007 # write updates to pending section
1009 my $modified = {}; # record what $option we modify
1011 foreach my $opt (@delete) {
1012 $modified->{$opt} = 1;
1013 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1014 if (!defined($conf->{$opt})) {
1015 warn "cannot delete '$opt' - not set in current configuration!\n";
1016 $modified->{$opt} = 0;
1020 if ($opt =~ m/^unused/) {
1021 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1022 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1023 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1024 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1025 delete $conf->{$opt};
1026 PVE
::QemuConfig-
>write_config($vmid, $conf);
1028 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1029 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1030 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1031 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1032 if defined($conf->{pending
}->{$opt});
1033 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1034 PVE
::QemuConfig-
>write_config($vmid, $conf);
1036 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1037 PVE
::QemuConfig-
>write_config($vmid, $conf);
1041 foreach my $opt (keys %$param) { # add/change
1042 $modified->{$opt} = 1;
1043 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1044 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1046 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1047 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1048 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1049 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1051 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1053 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1054 if defined($conf->{pending
}->{$opt});
1056 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1058 $conf->{pending
}->{$opt} = $param->{$opt};
1060 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1061 PVE
::QemuConfig-
>write_config($vmid, $conf);
1064 # remove pending changes when nothing changed
1065 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1066 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1067 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1069 return if !scalar(keys %{$conf->{pending
}});
1071 my $running = PVE
::QemuServer
::check_running
($vmid);
1073 # apply pending changes
1075 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1079 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1080 raise_param_exc
($errors) if scalar(keys %$errors);
1082 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1092 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1094 if ($background_delay) {
1096 # Note: It would be better to do that in the Event based HTTPServer
1097 # to avoid blocking call to sleep.
1099 my $end_time = time() + $background_delay;
1101 my $task = PVE
::Tools
::upid_decode
($upid);
1104 while (time() < $end_time) {
1105 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1107 sleep(1); # this gets interrupted when child process ends
1111 my $status = PVE
::Tools
::upid_read_status
($upid);
1112 return undef if $status eq 'OK';
1121 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1124 my $vm_config_perm_list = [
1129 'VM.Config.Network',
1131 'VM.Config.Options',
1134 __PACKAGE__-
>register_method({
1135 name
=> 'update_vm_async',
1136 path
=> '{vmid}/config',
1140 description
=> "Set virtual machine options (asynchrounous API).",
1142 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1145 additionalProperties
=> 0,
1146 properties
=> PVE
::QemuServer
::json_config_properties
(
1148 node
=> get_standard_option
('pve-node'),
1149 vmid
=> get_standard_option
('pve-vmid'),
1150 skiplock
=> get_standard_option
('skiplock'),
1152 type
=> 'string', format
=> 'pve-configid-list',
1153 description
=> "A list of settings you want to delete.",
1157 type
=> 'string', format
=> 'pve-configid-list',
1158 description
=> "Revert a pending change.",
1163 description
=> $opt_force_description,
1165 requires
=> 'delete',
1169 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1173 background_delay
=> {
1175 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1186 code
=> $update_vm_api,
1189 __PACKAGE__-
>register_method({
1190 name
=> 'update_vm',
1191 path
=> '{vmid}/config',
1195 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1197 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1200 additionalProperties
=> 0,
1201 properties
=> PVE
::QemuServer
::json_config_properties
(
1203 node
=> get_standard_option
('pve-node'),
1204 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1205 skiplock
=> get_standard_option
('skiplock'),
1207 type
=> 'string', format
=> 'pve-configid-list',
1208 description
=> "A list of settings you want to delete.",
1212 type
=> 'string', format
=> 'pve-configid-list',
1213 description
=> "Revert a pending change.",
1218 description
=> $opt_force_description,
1220 requires
=> 'delete',
1224 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1230 returns
=> { type
=> 'null' },
1233 &$update_vm_api($param, 1);
1239 __PACKAGE__-
>register_method({
1240 name
=> 'destroy_vm',
1245 description
=> "Destroy the vm (also delete all used/owned volumes).",
1247 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1250 additionalProperties
=> 0,
1252 node
=> get_standard_option
('pve-node'),
1253 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1254 skiplock
=> get_standard_option
('skiplock'),
1263 my $rpcenv = PVE
::RPCEnvironment
::get
();
1265 my $authuser = $rpcenv->get_user();
1267 my $vmid = $param->{vmid
};
1269 my $skiplock = $param->{skiplock
};
1270 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1271 if $skiplock && $authuser ne 'root@pam';
1274 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1276 my $storecfg = PVE
::Storage
::config
();
1278 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1280 die "unable to remove VM $vmid - used in HA resources\n"
1281 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1283 # do not allow destroy if there are replication jobs
1284 my $repl_conf = PVE
::ReplicationConfig-
>new();
1285 $repl_conf->check_for_existing_jobs($vmid);
1287 # early tests (repeat after locking)
1288 die "VM $vmid is running - destroy failed\n"
1289 if PVE
::QemuServer
::check_running
($vmid);
1294 syslog
('info', "destroy VM $vmid: $upid\n");
1296 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1298 PVE
::AccessControl
::remove_vm_access
($vmid);
1300 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1303 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1306 __PACKAGE__-
>register_method({
1308 path
=> '{vmid}/unlink',
1312 description
=> "Unlink/delete disk images.",
1314 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1317 additionalProperties
=> 0,
1319 node
=> get_standard_option
('pve-node'),
1320 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1322 type
=> 'string', format
=> 'pve-configid-list',
1323 description
=> "A list of disk IDs you want to delete.",
1327 description
=> $opt_force_description,
1332 returns
=> { type
=> 'null'},
1336 $param->{delete} = extract_param
($param, 'idlist');
1338 __PACKAGE__-
>update_vm($param);
1345 __PACKAGE__-
>register_method({
1347 path
=> '{vmid}/vncproxy',
1351 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1353 description
=> "Creates a TCP VNC proxy connections.",
1355 additionalProperties
=> 0,
1357 node
=> get_standard_option
('pve-node'),
1358 vmid
=> get_standard_option
('pve-vmid'),
1362 description
=> "starts websockify instead of vncproxy",
1367 additionalProperties
=> 0,
1369 user
=> { type
=> 'string' },
1370 ticket
=> { type
=> 'string' },
1371 cert
=> { type
=> 'string' },
1372 port
=> { type
=> 'integer' },
1373 upid
=> { type
=> 'string' },
1379 my $rpcenv = PVE
::RPCEnvironment
::get
();
1381 my $authuser = $rpcenv->get_user();
1383 my $vmid = $param->{vmid
};
1384 my $node = $param->{node
};
1385 my $websocket = $param->{websocket
};
1387 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1389 my $authpath = "/vms/$vmid";
1391 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1393 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1396 my ($remip, $family);
1399 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1400 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1401 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1402 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1404 $family = PVE
::Tools
::get_host_address_family
($node);
1407 my $port = PVE
::Tools
::next_vnc_port
($family);
1414 syslog
('info', "starting vnc proxy $upid\n");
1418 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1420 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1422 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1423 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1424 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1425 '-timeout', $timeout, '-authpath', $authpath,
1426 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1427 PVE
::Tools
::run_command
($cmd);
1430 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1432 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1434 my $sock = IO
::Socket
::IP-
>new(
1439 GetAddrInfoFlags
=> 0,
1440 ) or die "failed to create socket: $!\n";
1441 # Inside the worker we shouldn't have any previous alarms
1442 # running anyway...:
1444 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1446 accept(my $cli, $sock) or die "connection failed: $!\n";
1449 if (PVE
::Tools
::run_command
($cmd,
1450 output
=> '>&'.fileno($cli),
1451 input
=> '<&'.fileno($cli),
1454 die "Failed to run vncproxy.\n";
1461 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1463 PVE
::Tools
::wait_for_vnc_port
($port);
1474 __PACKAGE__-
>register_method({
1475 name
=> 'vncwebsocket',
1476 path
=> '{vmid}/vncwebsocket',
1479 description
=> "You also need to pass a valid ticket (vncticket).",
1480 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1482 description
=> "Opens a weksocket for VNC traffic.",
1484 additionalProperties
=> 0,
1486 node
=> get_standard_option
('pve-node'),
1487 vmid
=> get_standard_option
('pve-vmid'),
1489 description
=> "Ticket from previous call to vncproxy.",
1494 description
=> "Port number returned by previous vncproxy call.",
1504 port
=> { type
=> 'string' },
1510 my $rpcenv = PVE
::RPCEnvironment
::get
();
1512 my $authuser = $rpcenv->get_user();
1514 my $vmid = $param->{vmid
};
1515 my $node = $param->{node
};
1517 my $authpath = "/vms/$vmid";
1519 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1521 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1523 # Note: VNC ports are acessible from outside, so we do not gain any
1524 # security if we verify that $param->{port} belongs to VM $vmid. This
1525 # check is done by verifying the VNC ticket (inside VNC protocol).
1527 my $port = $param->{port
};
1529 return { port
=> $port };
1532 __PACKAGE__-
>register_method({
1533 name
=> 'spiceproxy',
1534 path
=> '{vmid}/spiceproxy',
1539 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1541 description
=> "Returns a SPICE configuration to connect to the VM.",
1543 additionalProperties
=> 0,
1545 node
=> get_standard_option
('pve-node'),
1546 vmid
=> get_standard_option
('pve-vmid'),
1547 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1550 returns
=> get_standard_option
('remote-viewer-config'),
1554 my $rpcenv = PVE
::RPCEnvironment
::get
();
1556 my $authuser = $rpcenv->get_user();
1558 my $vmid = $param->{vmid
};
1559 my $node = $param->{node
};
1560 my $proxy = $param->{proxy
};
1562 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1563 my $title = "VM $vmid";
1564 $title .= " - ". $conf->{name
} if $conf->{name
};
1566 my $port = PVE
::QemuServer
::spice_port
($vmid);
1568 my ($ticket, undef, $remote_viewer_config) =
1569 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1571 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1572 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1574 return $remote_viewer_config;
1577 __PACKAGE__-
>register_method({
1579 path
=> '{vmid}/status',
1582 description
=> "Directory index",
1587 additionalProperties
=> 0,
1589 node
=> get_standard_option
('pve-node'),
1590 vmid
=> get_standard_option
('pve-vmid'),
1598 subdir
=> { type
=> 'string' },
1601 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1607 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1610 { subdir
=> 'current' },
1611 { subdir
=> 'start' },
1612 { subdir
=> 'stop' },
1618 __PACKAGE__-
>register_method({
1619 name
=> 'vm_status',
1620 path
=> '{vmid}/status/current',
1623 protected
=> 1, # qemu pid files are only readable by root
1624 description
=> "Get virtual machine status.",
1626 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1629 additionalProperties
=> 0,
1631 node
=> get_standard_option
('pve-node'),
1632 vmid
=> get_standard_option
('pve-vmid'),
1635 returns
=> { type
=> 'object' },
1640 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1642 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1643 my $status = $vmstatus->{$param->{vmid
}};
1645 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1647 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1652 __PACKAGE__-
>register_method({
1654 path
=> '{vmid}/status/start',
1658 description
=> "Start virtual machine.",
1660 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1663 additionalProperties
=> 0,
1665 node
=> get_standard_option
('pve-node'),
1666 vmid
=> get_standard_option
('pve-vmid',
1667 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1668 skiplock
=> get_standard_option
('skiplock'),
1669 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1670 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1673 enum
=> ['secure', 'insecure'],
1674 description
=> "Migration traffic is encrypted using an SSH " .
1675 "tunnel by default. On secure, completely private networks " .
1676 "this can be disabled to increase performance.",
1679 migration_network
=> {
1680 type
=> 'string', format
=> 'CIDR',
1681 description
=> "CIDR of the (sub) network that is used for migration.",
1684 machine
=> get_standard_option
('pve-qm-machine'),
1686 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1698 my $rpcenv = PVE
::RPCEnvironment
::get
();
1700 my $authuser = $rpcenv->get_user();
1702 my $node = extract_param
($param, 'node');
1704 my $vmid = extract_param
($param, 'vmid');
1706 my $machine = extract_param
($param, 'machine');
1708 my $stateuri = extract_param
($param, 'stateuri');
1709 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1710 if $stateuri && $authuser ne 'root@pam';
1712 my $skiplock = extract_param
($param, 'skiplock');
1713 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1714 if $skiplock && $authuser ne 'root@pam';
1716 my $migratedfrom = extract_param
($param, 'migratedfrom');
1717 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1718 if $migratedfrom && $authuser ne 'root@pam';
1720 my $migration_type = extract_param
($param, 'migration_type');
1721 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1722 if $migration_type && $authuser ne 'root@pam';
1724 my $migration_network = extract_param
($param, 'migration_network');
1725 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1726 if $migration_network && $authuser ne 'root@pam';
1728 my $targetstorage = extract_param
($param, 'targetstorage');
1729 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1730 if $targetstorage && $authuser ne 'root@pam';
1732 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1733 if $targetstorage && !$migratedfrom;
1735 # read spice ticket from STDIN
1737 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1738 if (defined(my $line = <>)) {
1740 $spice_ticket = $line;
1744 PVE
::Cluster
::check_cfs_quorum
();
1746 my $storecfg = PVE
::Storage
::config
();
1748 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1749 $rpcenv->{type
} ne 'ha') {
1754 my $service = "vm:$vmid";
1756 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1758 print "Executing HA start for VM $vmid\n";
1760 PVE
::Tools
::run_command
($cmd);
1765 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1772 syslog
('info', "start VM $vmid: $upid\n");
1774 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1775 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1780 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1784 __PACKAGE__-
>register_method({
1786 path
=> '{vmid}/status/stop',
1790 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1791 "is akin to pulling the power plug of a running computer and may damage the VM data",
1793 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1796 additionalProperties
=> 0,
1798 node
=> get_standard_option
('pve-node'),
1799 vmid
=> get_standard_option
('pve-vmid',
1800 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1801 skiplock
=> get_standard_option
('skiplock'),
1802 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1804 description
=> "Wait maximal timeout seconds.",
1810 description
=> "Do not deactivate storage volumes.",
1823 my $rpcenv = PVE
::RPCEnvironment
::get
();
1825 my $authuser = $rpcenv->get_user();
1827 my $node = extract_param
($param, 'node');
1829 my $vmid = extract_param
($param, 'vmid');
1831 my $skiplock = extract_param
($param, 'skiplock');
1832 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1833 if $skiplock && $authuser ne 'root@pam';
1835 my $keepActive = extract_param
($param, 'keepActive');
1836 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1837 if $keepActive && $authuser ne 'root@pam';
1839 my $migratedfrom = extract_param
($param, 'migratedfrom');
1840 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1841 if $migratedfrom && $authuser ne 'root@pam';
1844 my $storecfg = PVE
::Storage
::config
();
1846 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1851 my $service = "vm:$vmid";
1853 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1855 print "Executing HA stop for VM $vmid\n";
1857 PVE
::Tools
::run_command
($cmd);
1862 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1868 syslog
('info', "stop VM $vmid: $upid\n");
1870 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1871 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1876 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1880 __PACKAGE__-
>register_method({
1882 path
=> '{vmid}/status/reset',
1886 description
=> "Reset virtual machine.",
1888 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1891 additionalProperties
=> 0,
1893 node
=> get_standard_option
('pve-node'),
1894 vmid
=> get_standard_option
('pve-vmid',
1895 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1896 skiplock
=> get_standard_option
('skiplock'),
1905 my $rpcenv = PVE
::RPCEnvironment
::get
();
1907 my $authuser = $rpcenv->get_user();
1909 my $node = extract_param
($param, 'node');
1911 my $vmid = extract_param
($param, 'vmid');
1913 my $skiplock = extract_param
($param, 'skiplock');
1914 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1915 if $skiplock && $authuser ne 'root@pam';
1917 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1922 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1927 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1930 __PACKAGE__-
>register_method({
1931 name
=> 'vm_shutdown',
1932 path
=> '{vmid}/status/shutdown',
1936 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1937 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1939 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1942 additionalProperties
=> 0,
1944 node
=> get_standard_option
('pve-node'),
1945 vmid
=> get_standard_option
('pve-vmid',
1946 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1947 skiplock
=> get_standard_option
('skiplock'),
1949 description
=> "Wait maximal timeout seconds.",
1955 description
=> "Make sure the VM stops.",
1961 description
=> "Do not deactivate storage volumes.",
1974 my $rpcenv = PVE
::RPCEnvironment
::get
();
1976 my $authuser = $rpcenv->get_user();
1978 my $node = extract_param
($param, 'node');
1980 my $vmid = extract_param
($param, 'vmid');
1982 my $skiplock = extract_param
($param, 'skiplock');
1983 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1984 if $skiplock && $authuser ne 'root@pam';
1986 my $keepActive = extract_param
($param, 'keepActive');
1987 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1988 if $keepActive && $authuser ne 'root@pam';
1990 my $storecfg = PVE
::Storage
::config
();
1994 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1995 # otherwise, we will infer a shutdown command, but run into the timeout,
1996 # then when the vm is resumed, it will instantly shutdown
1998 # checking the qmp status here to get feedback to the gui/cli/api
1999 # and the status query should not take too long
2002 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2006 if (!$err && $qmpstatus->{status
} eq "paused") {
2007 if ($param->{forceStop
}) {
2008 warn "VM is paused - stop instead of shutdown\n";
2011 die "VM is paused - cannot shutdown\n";
2015 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2016 ($rpcenv->{type
} ne 'ha')) {
2021 my $service = "vm:$vmid";
2023 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2025 print "Executing HA stop for VM $vmid\n";
2027 PVE
::Tools
::run_command
($cmd);
2032 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2039 syslog
('info', "shutdown VM $vmid: $upid\n");
2041 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2042 $shutdown, $param->{forceStop
}, $keepActive);
2047 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2051 __PACKAGE__-
>register_method({
2052 name
=> 'vm_suspend',
2053 path
=> '{vmid}/status/suspend',
2057 description
=> "Suspend virtual machine.",
2059 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2062 additionalProperties
=> 0,
2064 node
=> get_standard_option
('pve-node'),
2065 vmid
=> get_standard_option
('pve-vmid',
2066 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2067 skiplock
=> get_standard_option
('skiplock'),
2076 my $rpcenv = PVE
::RPCEnvironment
::get
();
2078 my $authuser = $rpcenv->get_user();
2080 my $node = extract_param
($param, 'node');
2082 my $vmid = extract_param
($param, 'vmid');
2084 my $skiplock = extract_param
($param, 'skiplock');
2085 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2086 if $skiplock && $authuser ne 'root@pam';
2088 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2093 syslog
('info', "suspend VM $vmid: $upid\n");
2095 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2100 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2103 __PACKAGE__-
>register_method({
2104 name
=> 'vm_resume',
2105 path
=> '{vmid}/status/resume',
2109 description
=> "Resume virtual machine.",
2111 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2114 additionalProperties
=> 0,
2116 node
=> get_standard_option
('pve-node'),
2117 vmid
=> get_standard_option
('pve-vmid',
2118 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2119 skiplock
=> get_standard_option
('skiplock'),
2120 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2130 my $rpcenv = PVE
::RPCEnvironment
::get
();
2132 my $authuser = $rpcenv->get_user();
2134 my $node = extract_param
($param, 'node');
2136 my $vmid = extract_param
($param, 'vmid');
2138 my $skiplock = extract_param
($param, 'skiplock');
2139 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2140 if $skiplock && $authuser ne 'root@pam';
2142 my $nocheck = extract_param
($param, 'nocheck');
2144 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2149 syslog
('info', "resume VM $vmid: $upid\n");
2151 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2156 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2159 __PACKAGE__-
>register_method({
2160 name
=> 'vm_sendkey',
2161 path
=> '{vmid}/sendkey',
2165 description
=> "Send key event to virtual machine.",
2167 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2170 additionalProperties
=> 0,
2172 node
=> get_standard_option
('pve-node'),
2173 vmid
=> get_standard_option
('pve-vmid',
2174 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2175 skiplock
=> get_standard_option
('skiplock'),
2177 description
=> "The key (qemu monitor encoding).",
2182 returns
=> { type
=> 'null'},
2186 my $rpcenv = PVE
::RPCEnvironment
::get
();
2188 my $authuser = $rpcenv->get_user();
2190 my $node = extract_param
($param, 'node');
2192 my $vmid = extract_param
($param, 'vmid');
2194 my $skiplock = extract_param
($param, 'skiplock');
2195 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2196 if $skiplock && $authuser ne 'root@pam';
2198 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2203 __PACKAGE__-
>register_method({
2204 name
=> 'vm_feature',
2205 path
=> '{vmid}/feature',
2209 description
=> "Check if feature for virtual machine is available.",
2211 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2214 additionalProperties
=> 0,
2216 node
=> get_standard_option
('pve-node'),
2217 vmid
=> get_standard_option
('pve-vmid'),
2219 description
=> "Feature to check.",
2221 enum
=> [ 'snapshot', 'clone', 'copy' ],
2223 snapname
=> get_standard_option
('pve-snapshot-name', {
2231 hasFeature
=> { type
=> 'boolean' },
2234 items
=> { type
=> 'string' },
2241 my $node = extract_param
($param, 'node');
2243 my $vmid = extract_param
($param, 'vmid');
2245 my $snapname = extract_param
($param, 'snapname');
2247 my $feature = extract_param
($param, 'feature');
2249 my $running = PVE
::QemuServer
::check_running
($vmid);
2251 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2254 my $snap = $conf->{snapshots
}->{$snapname};
2255 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2258 my $storecfg = PVE
::Storage
::config
();
2260 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2261 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2264 hasFeature
=> $hasFeature,
2265 nodes
=> [ keys %$nodelist ],
2269 __PACKAGE__-
>register_method({
2271 path
=> '{vmid}/clone',
2275 description
=> "Create a copy of virtual machine/template.",
2277 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2278 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2279 "'Datastore.AllocateSpace' on any used storage.",
2282 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2284 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2285 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2290 additionalProperties
=> 0,
2292 node
=> get_standard_option
('pve-node'),
2293 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2294 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2297 type
=> 'string', format
=> 'dns-name',
2298 description
=> "Set a name for the new VM.",
2303 description
=> "Description for the new VM.",
2307 type
=> 'string', format
=> 'pve-poolid',
2308 description
=> "Add the new VM to the specified pool.",
2310 snapname
=> get_standard_option
('pve-snapshot-name', {
2313 storage
=> get_standard_option
('pve-storage-id', {
2314 description
=> "Target storage for full clone.",
2319 description
=> "Target format for file storage.",
2323 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2328 description
=> "Create a full copy of all disk. This is always done when " .
2329 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2332 target
=> get_standard_option
('pve-node', {
2333 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2344 my $rpcenv = PVE
::RPCEnvironment
::get
();
2346 my $authuser = $rpcenv->get_user();
2348 my $node = extract_param
($param, 'node');
2350 my $vmid = extract_param
($param, 'vmid');
2352 my $newid = extract_param
($param, 'newid');
2354 my $pool = extract_param
($param, 'pool');
2356 if (defined($pool)) {
2357 $rpcenv->check_pool_exist($pool);
2360 my $snapname = extract_param
($param, 'snapname');
2362 my $storage = extract_param
($param, 'storage');
2364 my $format = extract_param
($param, 'format');
2366 my $target = extract_param
($param, 'target');
2368 my $localnode = PVE
::INotify
::nodename
();
2370 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2372 PVE
::Cluster
::check_node_exists
($target) if $target;
2374 my $storecfg = PVE
::Storage
::config
();
2377 # check if storage is enabled on local node
2378 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2380 # check if storage is available on target node
2381 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2382 # clone only works if target storage is shared
2383 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2384 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2388 PVE
::Cluster
::check_cfs_quorum
();
2390 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2392 # exclusive lock if VM is running - else shared lock is enough;
2393 my $shared_lock = $running ?
0 : 1;
2397 # do all tests after lock
2398 # we also try to do all tests before we fork the worker
2400 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2402 PVE
::QemuConfig-
>check_lock($conf);
2404 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2406 die "unexpected state change\n" if $verify_running != $running;
2408 die "snapshot '$snapname' does not exist\n"
2409 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2411 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2413 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2415 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2417 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2419 die "unable to create VM $newid: config file already exists\n"
2422 my $newconf = { lock => 'clone' };
2427 foreach my $opt (keys %$oldconf) {
2428 my $value = $oldconf->{$opt};
2430 # do not copy snapshot related info
2431 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2432 $opt eq 'vmstate' || $opt eq 'snapstate';
2434 # no need to copy unused images, because VMID(owner) changes anyways
2435 next if $opt =~ m/^unused\d+$/;
2437 # always change MAC! address
2438 if ($opt =~ m/^net(\d+)$/) {
2439 my $net = PVE
::QemuServer
::parse_net
($value);
2440 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2441 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2442 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2443 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2444 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2445 die "unable to parse drive options for '$opt'\n" if !$drive;
2446 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2447 $newconf->{$opt} = $value; # simply copy configuration
2449 if ($param->{full
}) {
2450 die "Full clone feature is not supported for drive '$opt'\n"
2451 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2452 $fullclone->{$opt} = 1;
2454 # not full means clone instead of copy
2455 die "Linked clone feature is not supported for drive '$opt'\n"
2456 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2458 $drives->{$opt} = $drive;
2459 push @$vollist, $drive->{file
};
2462 # copy everything else
2463 $newconf->{$opt} = $value;
2467 # auto generate a new uuid
2468 my ($uuid, $uuid_str);
2469 UUID
::generate
($uuid);
2470 UUID
::unparse
($uuid, $uuid_str);
2471 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2472 $smbios1->{uuid
} = $uuid_str;
2473 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2475 delete $newconf->{template
};
2477 if ($param->{name
}) {
2478 $newconf->{name
} = $param->{name
};
2480 if ($oldconf->{name
}) {
2481 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2483 $newconf->{name
} = "Copy-of-VM-$vmid";
2487 if ($param->{description
}) {
2488 $newconf->{description
} = $param->{description
};
2491 # create empty/temp config - this fails if VM already exists on other node
2492 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2497 my $newvollist = [];
2501 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2503 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2505 my $total_jobs = scalar(keys %{$drives});
2508 foreach my $opt (keys %$drives) {
2509 my $drive = $drives->{$opt};
2510 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2512 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2513 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2514 $jobs, $skipcomplete, $oldconf->{agent
});
2516 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2518 PVE
::QemuConfig-
>write_config($newid, $newconf);
2522 delete $newconf->{lock};
2523 PVE
::QemuConfig-
>write_config($newid, $newconf);
2526 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2527 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2528 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2530 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2531 die "Failed to move config to node '$target' - rename failed: $!\n"
2532 if !rename($conffile, $newconffile);
2535 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2540 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2542 sleep 1; # some storage like rbd need to wait before release volume - really?
2544 foreach my $volid (@$newvollist) {
2545 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2548 die "clone failed: $err";
2554 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2556 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2559 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2560 # Aquire exclusive lock lock for $newid
2561 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2566 __PACKAGE__-
>register_method({
2567 name
=> 'move_vm_disk',
2568 path
=> '{vmid}/move_disk',
2572 description
=> "Move volume to different storage.",
2574 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2576 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2577 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2581 additionalProperties
=> 0,
2583 node
=> get_standard_option
('pve-node'),
2584 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2587 description
=> "The disk you want to move.",
2588 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2590 storage
=> get_standard_option
('pve-storage-id', {
2591 description
=> "Target storage.",
2592 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2596 description
=> "Target Format.",
2597 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2602 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2608 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2616 description
=> "the task ID.",
2621 my $rpcenv = PVE
::RPCEnvironment
::get
();
2623 my $authuser = $rpcenv->get_user();
2625 my $node = extract_param
($param, 'node');
2627 my $vmid = extract_param
($param, 'vmid');
2629 my $digest = extract_param
($param, 'digest');
2631 my $disk = extract_param
($param, 'disk');
2633 my $storeid = extract_param
($param, 'storage');
2635 my $format = extract_param
($param, 'format');
2637 my $storecfg = PVE
::Storage
::config
();
2639 my $updatefn = sub {
2641 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2643 PVE
::QemuConfig-
>check_lock($conf);
2645 die "checksum missmatch (file change by other user?)\n"
2646 if $digest && $digest ne $conf->{digest
};
2648 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2650 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2652 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2654 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2657 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2658 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2662 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2663 (!$format || !$oldfmt || $oldfmt eq $format);
2665 # this only checks snapshots because $disk is passed!
2666 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2667 die "you can't move a disk with snapshots and delete the source\n"
2668 if $snapshotted && $param->{delete};
2670 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2672 my $running = PVE
::QemuServer
::check_running
($vmid);
2674 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2678 my $newvollist = [];
2681 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2683 warn "moving disk with snapshots, snapshots will not be moved!\n"
2686 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2687 $vmid, $storeid, $format, 1, $newvollist);
2689 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2691 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2693 # convert moved disk to base if part of template
2694 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2695 if PVE
::QemuConfig-
>is_template($conf);
2697 PVE
::QemuConfig-
>write_config($vmid, $conf);
2700 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2701 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2708 foreach my $volid (@$newvollist) {
2709 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2712 die "storage migration failed: $err";
2715 if ($param->{delete}) {
2717 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2718 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2724 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2727 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2730 __PACKAGE__-
>register_method({
2731 name
=> 'migrate_vm',
2732 path
=> '{vmid}/migrate',
2736 description
=> "Migrate virtual machine. Creates a new migration task.",
2738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2741 additionalProperties
=> 0,
2743 node
=> get_standard_option
('pve-node'),
2744 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2745 target
=> get_standard_option
('pve-node', {
2746 description
=> "Target node.",
2747 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2751 description
=> "Use online/live migration.",
2756 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2761 enum
=> ['secure', 'insecure'],
2762 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2765 migration_network
=> {
2766 type
=> 'string', format
=> 'CIDR',
2767 description
=> "CIDR of the (sub) network that is used for migration.",
2770 "with-local-disks" => {
2772 description
=> "Enable live storage migration for local disk",
2775 targetstorage
=> get_standard_option
('pve-storage-id', {
2776 description
=> "Default target storage.",
2778 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2784 description
=> "the task ID.",
2789 my $rpcenv = PVE
::RPCEnvironment
::get
();
2791 my $authuser = $rpcenv->get_user();
2793 my $target = extract_param
($param, 'target');
2795 my $localnode = PVE
::INotify
::nodename
();
2796 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2798 PVE
::Cluster
::check_cfs_quorum
();
2800 PVE
::Cluster
::check_node_exists
($target);
2802 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2804 my $vmid = extract_param
($param, 'vmid');
2806 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2807 if !$param->{online
} && $param->{targetstorage
};
2809 raise_param_exc
({ force
=> "Only root may use this option." })
2810 if $param->{force
} && $authuser ne 'root@pam';
2812 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2813 if $param->{migration_type
} && $authuser ne 'root@pam';
2815 # allow root only until better network permissions are available
2816 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2817 if $param->{migration_network
} && $authuser ne 'root@pam';
2820 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2822 # try to detect errors early
2824 PVE
::QemuConfig-
>check_lock($conf);
2826 if (PVE
::QemuServer
::check_running
($vmid)) {
2827 die "cant migrate running VM without --online\n"
2828 if !$param->{online
};
2831 my $storecfg = PVE
::Storage
::config
();
2833 if( $param->{targetstorage
}) {
2834 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2836 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2839 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2844 my $service = "vm:$vmid";
2846 my $cmd = ['ha-manager', 'migrate', $service, $target];
2848 print "Executing HA migrate for VM $vmid to node $target\n";
2850 PVE
::Tools
::run_command
($cmd);
2855 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2862 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2865 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2870 __PACKAGE__-
>register_method({
2872 path
=> '{vmid}/monitor',
2876 description
=> "Execute Qemu monitor commands.",
2878 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2879 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2882 additionalProperties
=> 0,
2884 node
=> get_standard_option
('pve-node'),
2885 vmid
=> get_standard_option
('pve-vmid'),
2888 description
=> "The monitor command.",
2892 returns
=> { type
=> 'string'},
2896 my $rpcenv = PVE
::RPCEnvironment
::get
();
2897 my $authuser = $rpcenv->get_user();
2900 my $command = shift;
2901 return $command =~ m/^\s*info(\s+|$)/
2902 || $command =~ m/^\s*help\s*$/;
2905 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2906 if !&$is_ro($param->{command
});
2908 my $vmid = $param->{vmid
};
2910 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2914 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2916 $res = "ERROR: $@" if $@;
2921 my $guest_agent_commands = [
2929 'network-get-interfaces',
2932 'get-memory-blocks',
2933 'get-memory-block-info',
2940 __PACKAGE__-
>register_method({
2942 path
=> '{vmid}/agent',
2946 description
=> "Execute Qemu Guest Agent commands.",
2948 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2951 additionalProperties
=> 0,
2953 node
=> get_standard_option
('pve-node'),
2954 vmid
=> get_standard_option
('pve-vmid', {
2955 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2958 description
=> "The QGA command.",
2959 enum
=> $guest_agent_commands,
2965 description
=> "Returns an object with a single `result` property. The type of that
2966 property depends on the executed command.",
2971 my $vmid = $param->{vmid
};
2973 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2975 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2976 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2978 my $cmd = $param->{command
};
2980 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2982 return { result
=> $res };
2985 __PACKAGE__-
>register_method({
2986 name
=> 'resize_vm',
2987 path
=> '{vmid}/resize',
2991 description
=> "Extend volume size.",
2993 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2996 additionalProperties
=> 0,
2998 node
=> get_standard_option
('pve-node'),
2999 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3000 skiplock
=> get_standard_option
('skiplock'),
3003 description
=> "The disk you want to resize.",
3004 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3008 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3009 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.",
3013 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3019 returns
=> { type
=> 'null'},
3023 my $rpcenv = PVE
::RPCEnvironment
::get
();
3025 my $authuser = $rpcenv->get_user();
3027 my $node = extract_param
($param, 'node');
3029 my $vmid = extract_param
($param, 'vmid');
3031 my $digest = extract_param
($param, 'digest');
3033 my $disk = extract_param
($param, 'disk');
3035 my $sizestr = extract_param
($param, 'size');
3037 my $skiplock = extract_param
($param, 'skiplock');
3038 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3039 if $skiplock && $authuser ne 'root@pam';
3041 my $storecfg = PVE
::Storage
::config
();
3043 my $updatefn = sub {
3045 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3047 die "checksum missmatch (file change by other user?)\n"
3048 if $digest && $digest ne $conf->{digest
};
3049 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3051 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3053 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3055 my (undef, undef, undef, undef, undef, undef, $format) =
3056 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3058 die "can't resize volume: $disk if snapshot exists\n"
3059 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3061 my $volid = $drive->{file
};
3063 die "disk '$disk' has no associated volume\n" if !$volid;
3065 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3067 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3069 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3071 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3072 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3074 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3075 my ($ext, $newsize, $unit) = ($1, $2, $4);
3078 $newsize = $newsize * 1024;
3079 } elsif ($unit eq 'M') {
3080 $newsize = $newsize * 1024 * 1024;
3081 } elsif ($unit eq 'G') {
3082 $newsize = $newsize * 1024 * 1024 * 1024;
3083 } elsif ($unit eq 'T') {
3084 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3087 $newsize += $size if $ext;
3088 $newsize = int($newsize);
3090 die "shrinking disks is not supported\n" if $newsize < $size;
3092 return if $size == $newsize;
3094 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3096 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3098 $drive->{size
} = $newsize;
3099 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3101 PVE
::QemuConfig-
>write_config($vmid, $conf);
3104 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3108 __PACKAGE__-
>register_method({
3109 name
=> 'snapshot_list',
3110 path
=> '{vmid}/snapshot',
3112 description
=> "List all snapshots.",
3114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3117 protected
=> 1, # qemu pid files are only readable by root
3119 additionalProperties
=> 0,
3121 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3122 node
=> get_standard_option
('pve-node'),
3131 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3136 my $vmid = $param->{vmid
};
3138 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3139 my $snaphash = $conf->{snapshots
} || {};
3143 foreach my $name (keys %$snaphash) {
3144 my $d = $snaphash->{$name};
3147 snaptime
=> $d->{snaptime
} || 0,
3148 vmstate
=> $d->{vmstate
} ?
1 : 0,
3149 description
=> $d->{description
} || '',
3151 $item->{parent
} = $d->{parent
} if $d->{parent
};
3152 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3156 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3157 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3158 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3160 push @$res, $current;
3165 __PACKAGE__-
>register_method({
3167 path
=> '{vmid}/snapshot',
3171 description
=> "Snapshot a VM.",
3173 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3176 additionalProperties
=> 0,
3178 node
=> get_standard_option
('pve-node'),
3179 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3180 snapname
=> get_standard_option
('pve-snapshot-name'),
3184 description
=> "Save the vmstate",
3189 description
=> "A textual description or comment.",
3195 description
=> "the task ID.",
3200 my $rpcenv = PVE
::RPCEnvironment
::get
();
3202 my $authuser = $rpcenv->get_user();
3204 my $node = extract_param
($param, 'node');
3206 my $vmid = extract_param
($param, 'vmid');
3208 my $snapname = extract_param
($param, 'snapname');
3210 die "unable to use snapshot name 'current' (reserved name)\n"
3211 if $snapname eq 'current';
3214 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3215 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3216 $param->{description
});
3219 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3222 __PACKAGE__-
>register_method({
3223 name
=> 'snapshot_cmd_idx',
3224 path
=> '{vmid}/snapshot/{snapname}',
3231 additionalProperties
=> 0,
3233 vmid
=> get_standard_option
('pve-vmid'),
3234 node
=> get_standard_option
('pve-node'),
3235 snapname
=> get_standard_option
('pve-snapshot-name'),
3244 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3251 push @$res, { cmd
=> 'rollback' };
3252 push @$res, { cmd
=> 'config' };
3257 __PACKAGE__-
>register_method({
3258 name
=> 'update_snapshot_config',
3259 path
=> '{vmid}/snapshot/{snapname}/config',
3263 description
=> "Update snapshot metadata.",
3265 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3268 additionalProperties
=> 0,
3270 node
=> get_standard_option
('pve-node'),
3271 vmid
=> get_standard_option
('pve-vmid'),
3272 snapname
=> get_standard_option
('pve-snapshot-name'),
3276 description
=> "A textual description or comment.",
3280 returns
=> { type
=> 'null' },
3284 my $rpcenv = PVE
::RPCEnvironment
::get
();
3286 my $authuser = $rpcenv->get_user();
3288 my $vmid = extract_param
($param, 'vmid');
3290 my $snapname = extract_param
($param, 'snapname');
3292 return undef if !defined($param->{description
});
3294 my $updatefn = sub {
3296 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3298 PVE
::QemuConfig-
>check_lock($conf);
3300 my $snap = $conf->{snapshots
}->{$snapname};
3302 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3304 $snap->{description
} = $param->{description
} if defined($param->{description
});
3306 PVE
::QemuConfig-
>write_config($vmid, $conf);
3309 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3314 __PACKAGE__-
>register_method({
3315 name
=> 'get_snapshot_config',
3316 path
=> '{vmid}/snapshot/{snapname}/config',
3319 description
=> "Get snapshot configuration",
3321 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3324 additionalProperties
=> 0,
3326 node
=> get_standard_option
('pve-node'),
3327 vmid
=> get_standard_option
('pve-vmid'),
3328 snapname
=> get_standard_option
('pve-snapshot-name'),
3331 returns
=> { type
=> "object" },
3335 my $rpcenv = PVE
::RPCEnvironment
::get
();
3337 my $authuser = $rpcenv->get_user();
3339 my $vmid = extract_param
($param, 'vmid');
3341 my $snapname = extract_param
($param, 'snapname');
3343 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3345 my $snap = $conf->{snapshots
}->{$snapname};
3347 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3352 __PACKAGE__-
>register_method({
3354 path
=> '{vmid}/snapshot/{snapname}/rollback',
3358 description
=> "Rollback VM state to specified snapshot.",
3360 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3363 additionalProperties
=> 0,
3365 node
=> get_standard_option
('pve-node'),
3366 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3367 snapname
=> get_standard_option
('pve-snapshot-name'),
3372 description
=> "the task ID.",
3377 my $rpcenv = PVE
::RPCEnvironment
::get
();
3379 my $authuser = $rpcenv->get_user();
3381 my $node = extract_param
($param, 'node');
3383 my $vmid = extract_param
($param, 'vmid');
3385 my $snapname = extract_param
($param, 'snapname');
3388 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3389 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3392 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3395 __PACKAGE__-
>register_method({
3396 name
=> 'delsnapshot',
3397 path
=> '{vmid}/snapshot/{snapname}',
3401 description
=> "Delete a VM snapshot.",
3403 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3406 additionalProperties
=> 0,
3408 node
=> get_standard_option
('pve-node'),
3409 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3410 snapname
=> get_standard_option
('pve-snapshot-name'),
3414 description
=> "For removal from config file, even if removing disk snapshots fails.",
3420 description
=> "the task ID.",
3425 my $rpcenv = PVE
::RPCEnvironment
::get
();
3427 my $authuser = $rpcenv->get_user();
3429 my $node = extract_param
($param, 'node');
3431 my $vmid = extract_param
($param, 'vmid');
3433 my $snapname = extract_param
($param, 'snapname');
3436 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3437 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3440 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3443 __PACKAGE__-
>register_method({
3445 path
=> '{vmid}/template',
3449 description
=> "Create a Template.",
3451 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3452 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3455 additionalProperties
=> 0,
3457 node
=> get_standard_option
('pve-node'),
3458 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3462 description
=> "If you want to convert only 1 disk to base image.",
3463 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3468 returns
=> { type
=> 'null'},
3472 my $rpcenv = PVE
::RPCEnvironment
::get
();
3474 my $authuser = $rpcenv->get_user();
3476 my $node = extract_param
($param, 'node');
3478 my $vmid = extract_param
($param, 'vmid');
3480 my $disk = extract_param
($param, 'disk');
3482 my $updatefn = sub {
3484 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3486 PVE
::QemuConfig-
>check_lock($conf);
3488 die "unable to create template, because VM contains snapshots\n"
3489 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3491 die "you can't convert a template to a template\n"
3492 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3494 die "you can't convert a VM to template if VM is running\n"
3495 if PVE
::QemuServer
::check_running
($vmid);
3498 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3501 $conf->{template
} = 1;
3502 PVE
::QemuConfig-
>write_config($vmid, $conf);
3504 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3507 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);