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);
21 use PVE
::RPCEnvironment
;
22 use PVE
::AccessControl
;
26 use PVE
::API2
::Firewall
::VM
;
27 use PVE
::ReplicationTools
;
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) = @_;
123 PVE
::QemuServer
::foreach_drive
($settings, sub {
124 my ($ds, $disk) = @_;
126 my $volid = $disk->{file
};
128 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
129 delete $disk->{size
};
130 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
131 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
132 my ($storeid, $size) = ($2 || $default_storage, $3);
133 die "no storage ID specified (and no default storage)\n" if !$storeid;
134 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
135 my $fmt = $disk->{format
} || $defformat;
138 if ($ds eq 'efidisk0') {
140 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
141 die "uefi vars image not found\n" if ! -f
$ovmfvars;
142 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
144 $disk->{file
} = $volid;
145 $disk->{size
} = 128*1024;
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
147 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
148 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
149 my $path = PVE
::Storage
::path
($storecfg, $volid);
150 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
151 push @$efidiskcmd, $ovmfvars;
152 push @$efidiskcmd, $path;
154 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
156 eval { PVE
::Tools
::run_command
($efidiskcmd); };
158 die "Copying of EFI Vars image failed: $err" if $err;
160 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
161 $fmt, undef, $size*1024*1024);
162 $disk->{file
} = $volid;
163 $disk->{size
} = $size*1024*1024*1024;
165 push @$vollist, $volid;
166 delete $disk->{format
}; # no longer needed
167 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
170 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
172 my $volid_is_new = 1;
175 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
176 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
181 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
183 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
185 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
187 die "volume $volid does not exists\n" if !$size;
189 $disk->{size
} = $size;
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
196 # free allocated images on error
198 syslog
('err', "VM $vmid creating disks failed");
199 foreach my $volid (@$vollist) {
200 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
206 # modify vm config if everything went well
207 foreach my $ds (keys %$res) {
208 $conf->{$ds} = $res->{$ds};
225 my $memoryoptions = {
231 my $hwtypeoptions = {
243 my $generaloptions = {
250 'migrate_downtime' => 1,
251 'migrate_speed' => 1,
263 my $vmpoweroptions = {
272 my $check_vm_modify_config_perm = sub {
273 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
275 return 1 if $authuser eq 'root@pam';
277 foreach my $opt (@$key_list) {
278 # disk checks need to be done somewhere else
279 next if PVE
::QemuServer
::is_valid_drivename
($opt);
280 next if $opt eq 'cdrom';
281 next if $opt =~ m/^unused\d+$/;
283 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
284 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
285 } elsif ($memoryoptions->{$opt}) {
286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
287 } elsif ($hwtypeoptions->{$opt}) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
289 } elsif ($generaloptions->{$opt}) {
290 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
291 # special case for startup since it changes host behaviour
292 if ($opt eq 'startup') {
293 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
295 } elsif ($vmpoweroptions->{$opt}) {
296 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
297 } elsif ($diskoptions->{$opt}) {
298 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
299 } elsif ($opt =~ m/^net\d+$/) {
300 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
302 # catches usb\d+, hostpci\d+, args, lock, etc.
303 # new options will be checked here
304 die "only root can set '$opt' config\n";
311 __PACKAGE__-
>register_method({
315 description
=> "Virtual machine index (per node).",
317 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
321 protected
=> 1, # qemu pid files are only readable by root
323 additionalProperties
=> 0,
325 node
=> get_standard_option
('pve-node'),
329 description
=> "Determine the full status of active VMs.",
339 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
344 my $rpcenv = PVE
::RPCEnvironment
::get
();
345 my $authuser = $rpcenv->get_user();
347 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
350 foreach my $vmid (keys %$vmstatus) {
351 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
353 my $data = $vmstatus->{$vmid};
354 $data->{vmid
} = int($vmid);
363 __PACKAGE__-
>register_method({
367 description
=> "Create or restore a virtual machine.",
369 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
370 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
371 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
372 user
=> 'all', # check inside
377 additionalProperties
=> 0,
378 properties
=> PVE
::QemuServer
::json_config_properties
(
380 node
=> get_standard_option
('pve-node'),
381 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
383 description
=> "The backup file.",
387 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
389 storage
=> get_standard_option
('pve-storage-id', {
390 description
=> "Default storage.",
392 completion
=> \
&PVE
::QemuServer
::complete_storage
,
397 description
=> "Allow to overwrite existing VM.",
398 requires
=> 'archive',
403 description
=> "Assign a unique random ethernet address.",
404 requires
=> 'archive',
408 type
=> 'string', format
=> 'pve-poolid',
409 description
=> "Add the VM to the specified pool.",
419 my $rpcenv = PVE
::RPCEnvironment
::get
();
421 my $authuser = $rpcenv->get_user();
423 my $node = extract_param
($param, 'node');
425 my $vmid = extract_param
($param, 'vmid');
427 my $archive = extract_param
($param, 'archive');
429 my $storage = extract_param
($param, 'storage');
431 my $force = extract_param
($param, 'force');
433 my $unique = extract_param
($param, 'unique');
435 my $pool = extract_param
($param, 'pool');
437 my $filename = PVE
::QemuConfig-
>config_file($vmid);
439 my $storecfg = PVE
::Storage
::config
();
441 PVE
::Cluster
::check_cfs_quorum
();
443 if (defined($pool)) {
444 $rpcenv->check_pool_exist($pool);
447 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
448 if defined($storage);
450 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
452 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
454 } elsif ($archive && $force && (-f
$filename) &&
455 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
456 # OK: user has VM.Backup permissions, and want to restore an existing VM
462 &$resolve_cdrom_alias($param);
464 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
466 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
468 foreach my $opt (keys %$param) {
469 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
470 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
471 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
473 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
474 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
478 PVE
::QemuServer
::add_random_macs
($param);
480 my $keystr = join(' ', keys %$param);
481 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
483 if ($archive eq '-') {
484 die "pipe requires cli environment\n"
485 if $rpcenv->{type
} ne 'cli';
487 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
488 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
492 my $restorefn = sub {
493 my $vmlist = PVE
::Cluster
::get_vmlist
();
494 if ($vmlist->{ids
}->{$vmid}) {
495 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
496 if ($current_node eq $node) {
497 my $conf = PVE
::QemuConfig-
>load_config($vmid);
499 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
501 die "unable to restore vm $vmid - config file already exists\n"
504 die "unable to restore vm $vmid - vm is running\n"
505 if PVE
::QemuServer
::check_running
($vmid);
507 die "unable to restore vm $vmid - vm is a template\n"
508 if PVE
::QemuConfig-
>is_template($conf);
511 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
516 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
519 unique
=> $unique });
521 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
524 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
530 PVE
::Cluster
::check_vmid_unused
($vmid);
540 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
542 # try to be smart about bootdisk
543 my @disks = PVE
::QemuServer
::valid_drive_names
();
545 foreach my $ds (reverse @disks) {
546 next if !$conf->{$ds};
547 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
548 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
552 if (!$conf->{bootdisk
} && $firstdisk) {
553 $conf->{bootdisk
} = $firstdisk;
556 # auto generate uuid if user did not specify smbios1 option
557 if (!$conf->{smbios1
}) {
558 my ($uuid, $uuid_str);
559 UUID
::generate
($uuid);
560 UUID
::unparse
($uuid, $uuid_str);
561 $conf->{smbios1
} = "uuid=$uuid_str";
564 PVE
::QemuConfig-
>write_config($vmid, $conf);
570 foreach my $volid (@$vollist) {
571 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
574 die "create failed - $err";
577 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
580 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
583 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
586 __PACKAGE__-
>register_method({
591 description
=> "Directory index",
596 additionalProperties
=> 0,
598 node
=> get_standard_option
('pve-node'),
599 vmid
=> get_standard_option
('pve-vmid'),
607 subdir
=> { type
=> 'string' },
610 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
616 { subdir
=> 'config' },
617 { subdir
=> 'pending' },
618 { subdir
=> 'status' },
619 { subdir
=> 'unlink' },
620 { subdir
=> 'vncproxy' },
621 { subdir
=> 'migrate' },
622 { subdir
=> 'resize' },
623 { subdir
=> 'move' },
625 { subdir
=> 'rrddata' },
626 { subdir
=> 'monitor' },
627 { subdir
=> 'agent' },
628 { subdir
=> 'snapshot' },
629 { subdir
=> 'spiceproxy' },
630 { subdir
=> 'sendkey' },
631 { subdir
=> 'firewall' },
637 __PACKAGE__-
>register_method ({
638 subclass
=> "PVE::API2::Firewall::VM",
639 path
=> '{vmid}/firewall',
642 __PACKAGE__-
>register_method({
644 path
=> '{vmid}/rrd',
646 protected
=> 1, # fixme: can we avoid that?
648 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
650 description
=> "Read VM RRD statistics (returns PNG)",
652 additionalProperties
=> 0,
654 node
=> get_standard_option
('pve-node'),
655 vmid
=> get_standard_option
('pve-vmid'),
657 description
=> "Specify the time frame you are interested in.",
659 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
662 description
=> "The list of datasources you want to display.",
663 type
=> 'string', format
=> 'pve-configid-list',
666 description
=> "The RRD consolidation function",
668 enum
=> [ 'AVERAGE', 'MAX' ],
676 filename
=> { type
=> 'string' },
682 return PVE
::Cluster
::create_rrd_graph
(
683 "pve2-vm/$param->{vmid}", $param->{timeframe
},
684 $param->{ds
}, $param->{cf
});
688 __PACKAGE__-
>register_method({
690 path
=> '{vmid}/rrddata',
692 protected
=> 1, # fixme: can we avoid that?
694 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
696 description
=> "Read VM RRD statistics",
698 additionalProperties
=> 0,
700 node
=> get_standard_option
('pve-node'),
701 vmid
=> get_standard_option
('pve-vmid'),
703 description
=> "Specify the time frame you are interested in.",
705 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
708 description
=> "The RRD consolidation function",
710 enum
=> [ 'AVERAGE', 'MAX' ],
725 return PVE
::Cluster
::create_rrd_data
(
726 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
730 __PACKAGE__-
>register_method({
732 path
=> '{vmid}/config',
735 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
737 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
740 additionalProperties
=> 0,
742 node
=> get_standard_option
('pve-node'),
743 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
745 description
=> "Get current values (instead of pending values).",
757 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
764 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
766 delete $conf->{snapshots
};
768 if (!$param->{current
}) {
769 foreach my $opt (keys %{$conf->{pending
}}) {
770 next if $opt eq 'delete';
771 my $value = $conf->{pending
}->{$opt};
772 next if ref($value); # just to be sure
773 $conf->{$opt} = $value;
775 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
776 foreach my $opt (keys %$pending_delete_hash) {
777 delete $conf->{$opt} if $conf->{$opt};
781 delete $conf->{pending
};
786 __PACKAGE__-
>register_method({
787 name
=> 'vm_pending',
788 path
=> '{vmid}/pending',
791 description
=> "Get virtual machine configuration, including pending changes.",
793 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
796 additionalProperties
=> 0,
798 node
=> get_standard_option
('pve-node'),
799 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
808 description
=> "Configuration option name.",
812 description
=> "Current value.",
817 description
=> "Pending value.",
822 description
=> "Indicates a pending delete request if present and not 0. " .
823 "The value 2 indicates a force-delete request.",
835 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
837 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
841 foreach my $opt (keys %$conf) {
842 next if ref($conf->{$opt});
843 my $item = { key
=> $opt };
844 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
845 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
846 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
850 foreach my $opt (keys %{$conf->{pending
}}) {
851 next if $opt eq 'delete';
852 next if ref($conf->{pending
}->{$opt}); # just to be sure
853 next if defined($conf->{$opt});
854 my $item = { key
=> $opt };
855 $item->{pending
} = $conf->{pending
}->{$opt};
859 while (my ($opt, $force) = each %$pending_delete_hash) {
860 next if $conf->{pending
}->{$opt}; # just to be sure
861 next if $conf->{$opt};
862 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
869 # POST/PUT {vmid}/config implementation
871 # The original API used PUT (idempotent) an we assumed that all operations
872 # are fast. But it turned out that almost any configuration change can
873 # involve hot-plug actions, or disk alloc/free. Such actions can take long
874 # time to complete and have side effects (not idempotent).
876 # The new implementation uses POST and forks a worker process. We added
877 # a new option 'background_delay'. If specified we wait up to
878 # 'background_delay' second for the worker task to complete. It returns null
879 # if the task is finished within that time, else we return the UPID.
881 my $update_vm_api = sub {
882 my ($param, $sync) = @_;
884 my $rpcenv = PVE
::RPCEnvironment
::get
();
886 my $authuser = $rpcenv->get_user();
888 my $node = extract_param
($param, 'node');
890 my $vmid = extract_param
($param, 'vmid');
892 my $digest = extract_param
($param, 'digest');
894 my $background_delay = extract_param
($param, 'background_delay');
896 my @paramarr = (); # used for log message
897 foreach my $key (keys %$param) {
898 push @paramarr, "-$key", $param->{$key};
901 my $skiplock = extract_param
($param, 'skiplock');
902 raise_param_exc
({ skiplock
=> "Only root may use this option." })
903 if $skiplock && $authuser ne 'root@pam';
905 my $delete_str = extract_param
($param, 'delete');
907 my $revert_str = extract_param
($param, 'revert');
909 my $force = extract_param
($param, 'force');
911 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
913 my $storecfg = PVE
::Storage
::config
();
915 my $defaults = PVE
::QemuServer
::load_defaults
();
917 &$resolve_cdrom_alias($param);
919 # now try to verify all parameters
922 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
923 if (!PVE
::QemuServer
::option_exists
($opt)) {
924 raise_param_exc
({ revert
=> "unknown option '$opt'" });
927 raise_param_exc
({ delete => "you can't use '-$opt' and " .
928 "-revert $opt' at the same time" })
929 if defined($param->{$opt});
935 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
936 $opt = 'ide2' if $opt eq 'cdrom';
938 raise_param_exc
({ delete => "you can't use '-$opt' and " .
939 "-delete $opt' at the same time" })
940 if defined($param->{$opt});
942 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
943 "-revert $opt' at the same time" })
946 if (!PVE
::QemuServer
::option_exists
($opt)) {
947 raise_param_exc
({ delete => "unknown option '$opt'" });
953 foreach my $opt (keys %$param) {
954 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
956 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
957 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
958 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
959 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
960 } elsif ($opt =~ m/^net(\d+)$/) {
962 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
963 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
967 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
969 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
971 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
975 my $conf = PVE
::QemuConfig-
>load_config($vmid);
977 die "checksum missmatch (file change by other user?)\n"
978 if $digest && $digest ne $conf->{digest
};
980 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
982 foreach my $opt (keys %$revert) {
983 if (defined($conf->{$opt})) {
984 $param->{$opt} = $conf->{$opt};
985 } elsif (defined($conf->{pending
}->{$opt})) {
990 if ($param->{memory
} || defined($param->{balloon
})) {
991 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
992 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
994 die "balloon value too large (must be smaller than assigned memory)\n"
995 if $balloon && $balloon > $maxmem;
998 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1002 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1004 # write updates to pending section
1006 my $modified = {}; # record what $option we modify
1008 foreach my $opt (@delete) {
1009 $modified->{$opt} = 1;
1010 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1011 if (!defined($conf->{$opt})) {
1012 warn "cannot delete '$opt' - not set in current configuration!\n";
1013 $modified->{$opt} = 0;
1017 if ($opt =~ m/^unused/) {
1018 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1019 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1020 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1021 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1022 delete $conf->{$opt};
1023 PVE
::QemuConfig-
>write_config($vmid, $conf);
1025 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1026 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1027 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1028 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1029 if defined($conf->{pending
}->{$opt});
1030 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1031 PVE
::QemuConfig-
>write_config($vmid, $conf);
1033 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1034 PVE
::QemuConfig-
>write_config($vmid, $conf);
1038 foreach my $opt (keys %$param) { # add/change
1039 $modified->{$opt} = 1;
1040 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1041 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1043 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1044 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1045 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1046 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1048 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1050 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1051 if defined($conf->{pending
}->{$opt});
1053 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1054 } elsif ($opt eq "replicate") {
1055 die "Not all volumes are syncable, please check your config\n"
1056 if !PVE
::ReplicationTools
::check_guest_volumes_syncable
($conf, 'qemu');
1057 my $repl = PVE
::JSONSchema
::check_format
('pve-replicate', $param->{opt
});
1058 PVE
::Cluster
::check_node_exists
($repl->{target
});
1059 $conf->{$opt} = $param->{$opt};
1061 $conf->{pending
}->{$opt} = $param->{$opt};
1063 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1064 PVE
::QemuConfig-
>write_config($vmid, $conf);
1067 # remove pending changes when nothing changed
1068 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1069 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1070 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1072 return if !scalar(keys %{$conf->{pending
}});
1074 my $running = PVE
::QemuServer
::check_running
($vmid);
1076 # apply pending changes
1078 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1082 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1083 raise_param_exc
($errors) if scalar(keys %$errors);
1085 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1095 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1097 if ($background_delay) {
1099 # Note: It would be better to do that in the Event based HTTPServer
1100 # to avoid blocking call to sleep.
1102 my $end_time = time() + $background_delay;
1104 my $task = PVE
::Tools
::upid_decode
($upid);
1107 while (time() < $end_time) {
1108 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1110 sleep(1); # this gets interrupted when child process ends
1114 my $status = PVE
::Tools
::upid_read_status
($upid);
1115 return undef if $status eq 'OK';
1124 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1127 my $vm_config_perm_list = [
1132 'VM.Config.Network',
1134 'VM.Config.Options',
1137 __PACKAGE__-
>register_method({
1138 name
=> 'update_vm_async',
1139 path
=> '{vmid}/config',
1143 description
=> "Set virtual machine options (asynchrounous API).",
1145 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1148 additionalProperties
=> 0,
1149 properties
=> PVE
::QemuServer
::json_config_properties
(
1151 node
=> get_standard_option
('pve-node'),
1152 vmid
=> get_standard_option
('pve-vmid'),
1153 skiplock
=> get_standard_option
('skiplock'),
1155 type
=> 'string', format
=> 'pve-configid-list',
1156 description
=> "A list of settings you want to delete.",
1160 type
=> 'string', format
=> 'pve-configid-list',
1161 description
=> "Revert a pending change.",
1166 description
=> $opt_force_description,
1168 requires
=> 'delete',
1172 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1176 background_delay
=> {
1178 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1189 code
=> $update_vm_api,
1192 __PACKAGE__-
>register_method({
1193 name
=> 'update_vm',
1194 path
=> '{vmid}/config',
1198 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1200 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1203 additionalProperties
=> 0,
1204 properties
=> PVE
::QemuServer
::json_config_properties
(
1206 node
=> get_standard_option
('pve-node'),
1207 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1208 skiplock
=> get_standard_option
('skiplock'),
1210 type
=> 'string', format
=> 'pve-configid-list',
1211 description
=> "A list of settings you want to delete.",
1215 type
=> 'string', format
=> 'pve-configid-list',
1216 description
=> "Revert a pending change.",
1221 description
=> $opt_force_description,
1223 requires
=> 'delete',
1227 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1233 returns
=> { type
=> 'null' },
1236 &$update_vm_api($param, 1);
1242 __PACKAGE__-
>register_method({
1243 name
=> 'destroy_vm',
1248 description
=> "Destroy the vm (also delete all used/owned volumes).",
1250 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1253 additionalProperties
=> 0,
1255 node
=> get_standard_option
('pve-node'),
1256 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1257 skiplock
=> get_standard_option
('skiplock'),
1266 my $rpcenv = PVE
::RPCEnvironment
::get
();
1268 my $authuser = $rpcenv->get_user();
1270 my $vmid = $param->{vmid
};
1272 my $skiplock = $param->{skiplock
};
1273 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1274 if $skiplock && $authuser ne 'root@pam';
1277 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1279 my $storecfg = PVE
::Storage
::config
();
1281 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1283 die "unable to remove VM $vmid - used in HA resources\n"
1284 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1286 # early tests (repeat after locking)
1287 die "VM $vmid is running - destroy failed\n"
1288 if PVE
::QemuServer
::check_running
($vmid);
1293 syslog
('info', "destroy VM $vmid: $upid\n");
1295 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1297 PVE
::AccessControl
::remove_vm_access
($vmid);
1299 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1302 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1305 __PACKAGE__-
>register_method({
1307 path
=> '{vmid}/unlink',
1311 description
=> "Unlink/delete disk images.",
1313 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1316 additionalProperties
=> 0,
1318 node
=> get_standard_option
('pve-node'),
1319 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1321 type
=> 'string', format
=> 'pve-configid-list',
1322 description
=> "A list of disk IDs you want to delete.",
1326 description
=> $opt_force_description,
1331 returns
=> { type
=> 'null'},
1335 $param->{delete} = extract_param
($param, 'idlist');
1337 __PACKAGE__-
>update_vm($param);
1344 __PACKAGE__-
>register_method({
1346 path
=> '{vmid}/vncproxy',
1350 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1352 description
=> "Creates a TCP VNC proxy connections.",
1354 additionalProperties
=> 0,
1356 node
=> get_standard_option
('pve-node'),
1357 vmid
=> get_standard_option
('pve-vmid'),
1361 description
=> "starts websockify instead of vncproxy",
1366 additionalProperties
=> 0,
1368 user
=> { type
=> 'string' },
1369 ticket
=> { type
=> 'string' },
1370 cert
=> { type
=> 'string' },
1371 port
=> { type
=> 'integer' },
1372 upid
=> { type
=> 'string' },
1378 my $rpcenv = PVE
::RPCEnvironment
::get
();
1380 my $authuser = $rpcenv->get_user();
1382 my $vmid = $param->{vmid
};
1383 my $node = $param->{node
};
1384 my $websocket = $param->{websocket
};
1386 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1388 my $authpath = "/vms/$vmid";
1390 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1392 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1395 my ($remip, $family);
1398 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1399 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1400 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1401 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1403 $family = PVE
::Tools
::get_host_address_family
($node);
1406 my $port = PVE
::Tools
::next_vnc_port
($family);
1413 syslog
('info', "starting vnc proxy $upid\n");
1417 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1419 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1421 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1422 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1423 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1424 '-timeout', $timeout, '-authpath', $authpath,
1425 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1426 PVE
::Tools
::run_command
($cmd);
1429 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1431 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1433 my $sock = IO
::Socket
::IP-
>new(
1437 GetAddrInfoFlags
=> 0,
1438 ) or die "failed to create socket: $!\n";
1439 # Inside the worker we shouldn't have any previous alarms
1440 # running anyway...:
1442 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1444 accept(my $cli, $sock) or die "connection failed: $!\n";
1447 if (PVE
::Tools
::run_command
($cmd,
1448 output
=> '>&'.fileno($cli),
1449 input
=> '<&'.fileno($cli),
1452 die "Failed to run vncproxy.\n";
1459 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1461 PVE
::Tools
::wait_for_vnc_port
($port);
1472 __PACKAGE__-
>register_method({
1473 name
=> 'vncwebsocket',
1474 path
=> '{vmid}/vncwebsocket',
1477 description
=> "You also need to pass a valid ticket (vncticket).",
1478 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1480 description
=> "Opens a weksocket for VNC traffic.",
1482 additionalProperties
=> 0,
1484 node
=> get_standard_option
('pve-node'),
1485 vmid
=> get_standard_option
('pve-vmid'),
1487 description
=> "Ticket from previous call to vncproxy.",
1492 description
=> "Port number returned by previous vncproxy call.",
1502 port
=> { type
=> 'string' },
1508 my $rpcenv = PVE
::RPCEnvironment
::get
();
1510 my $authuser = $rpcenv->get_user();
1512 my $vmid = $param->{vmid
};
1513 my $node = $param->{node
};
1515 my $authpath = "/vms/$vmid";
1517 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1519 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1521 # Note: VNC ports are acessible from outside, so we do not gain any
1522 # security if we verify that $param->{port} belongs to VM $vmid. This
1523 # check is done by verifying the VNC ticket (inside VNC protocol).
1525 my $port = $param->{port
};
1527 return { port
=> $port };
1530 __PACKAGE__-
>register_method({
1531 name
=> 'spiceproxy',
1532 path
=> '{vmid}/spiceproxy',
1537 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1539 description
=> "Returns a SPICE configuration to connect to the VM.",
1541 additionalProperties
=> 0,
1543 node
=> get_standard_option
('pve-node'),
1544 vmid
=> get_standard_option
('pve-vmid'),
1545 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1548 returns
=> get_standard_option
('remote-viewer-config'),
1552 my $rpcenv = PVE
::RPCEnvironment
::get
();
1554 my $authuser = $rpcenv->get_user();
1556 my $vmid = $param->{vmid
};
1557 my $node = $param->{node
};
1558 my $proxy = $param->{proxy
};
1560 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1561 my $title = "VM $vmid";
1562 $title .= " - ". $conf->{name
} if $conf->{name
};
1564 my $port = PVE
::QemuServer
::spice_port
($vmid);
1566 my ($ticket, undef, $remote_viewer_config) =
1567 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1569 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1570 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1572 return $remote_viewer_config;
1575 __PACKAGE__-
>register_method({
1577 path
=> '{vmid}/status',
1580 description
=> "Directory index",
1585 additionalProperties
=> 0,
1587 node
=> get_standard_option
('pve-node'),
1588 vmid
=> get_standard_option
('pve-vmid'),
1596 subdir
=> { type
=> 'string' },
1599 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1605 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1608 { subdir
=> 'current' },
1609 { subdir
=> 'start' },
1610 { subdir
=> 'stop' },
1616 __PACKAGE__-
>register_method({
1617 name
=> 'vm_status',
1618 path
=> '{vmid}/status/current',
1621 protected
=> 1, # qemu pid files are only readable by root
1622 description
=> "Get virtual machine status.",
1624 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1627 additionalProperties
=> 0,
1629 node
=> get_standard_option
('pve-node'),
1630 vmid
=> get_standard_option
('pve-vmid'),
1633 returns
=> { type
=> 'object' },
1638 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1640 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1641 my $status = $vmstatus->{$param->{vmid
}};
1643 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1645 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1650 __PACKAGE__-
>register_method({
1652 path
=> '{vmid}/status/start',
1656 description
=> "Start virtual machine.",
1658 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1661 additionalProperties
=> 0,
1663 node
=> get_standard_option
('pve-node'),
1664 vmid
=> get_standard_option
('pve-vmid',
1665 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1666 skiplock
=> get_standard_option
('skiplock'),
1667 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1668 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1671 enum
=> ['secure', 'insecure'],
1672 description
=> "Migration traffic is encrypted using an SSH " .
1673 "tunnel by default. On secure, completely private networks " .
1674 "this can be disabled to increase performance.",
1677 migration_network
=> {
1678 type
=> 'string', format
=> 'CIDR',
1679 description
=> "CIDR of the (sub) network that is used for migration.",
1682 machine
=> get_standard_option
('pve-qm-machine'),
1684 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1696 my $rpcenv = PVE
::RPCEnvironment
::get
();
1698 my $authuser = $rpcenv->get_user();
1700 my $node = extract_param
($param, 'node');
1702 my $vmid = extract_param
($param, 'vmid');
1704 my $machine = extract_param
($param, 'machine');
1706 my $stateuri = extract_param
($param, 'stateuri');
1707 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1708 if $stateuri && $authuser ne 'root@pam';
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 $migratedfrom = extract_param
($param, 'migratedfrom');
1715 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1716 if $migratedfrom && $authuser ne 'root@pam';
1718 my $migration_type = extract_param
($param, 'migration_type');
1719 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1720 if $migration_type && $authuser ne 'root@pam';
1722 my $migration_network = extract_param
($param, 'migration_network');
1723 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1724 if $migration_network && $authuser ne 'root@pam';
1726 my $targetstorage = extract_param
($param, 'targetstorage');
1727 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1728 if $targetstorage && $authuser ne 'root@pam';
1730 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1731 if $targetstorage && !$migratedfrom;
1733 # read spice ticket from STDIN
1735 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1736 if (defined(my $line = <>)) {
1738 $spice_ticket = $line;
1742 PVE
::Cluster
::check_cfs_quorum
();
1744 my $storecfg = PVE
::Storage
::config
();
1746 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1747 $rpcenv->{type
} ne 'ha') {
1752 my $service = "vm:$vmid";
1754 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1756 print "Executing HA start for VM $vmid\n";
1758 PVE
::Tools
::run_command
($cmd);
1763 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1770 syslog
('info', "start VM $vmid: $upid\n");
1772 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1773 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1778 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1782 __PACKAGE__-
>register_method({
1784 path
=> '{vmid}/status/stop',
1788 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1789 "is akin to pulling the power plug of a running computer and may damage the VM data",
1791 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1794 additionalProperties
=> 0,
1796 node
=> get_standard_option
('pve-node'),
1797 vmid
=> get_standard_option
('pve-vmid',
1798 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1799 skiplock
=> get_standard_option
('skiplock'),
1800 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1802 description
=> "Wait maximal timeout seconds.",
1808 description
=> "Do not deactivate storage volumes.",
1821 my $rpcenv = PVE
::RPCEnvironment
::get
();
1823 my $authuser = $rpcenv->get_user();
1825 my $node = extract_param
($param, 'node');
1827 my $vmid = extract_param
($param, 'vmid');
1829 my $skiplock = extract_param
($param, 'skiplock');
1830 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1831 if $skiplock && $authuser ne 'root@pam';
1833 my $keepActive = extract_param
($param, 'keepActive');
1834 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1835 if $keepActive && $authuser ne 'root@pam';
1837 my $migratedfrom = extract_param
($param, 'migratedfrom');
1838 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1839 if $migratedfrom && $authuser ne 'root@pam';
1842 my $storecfg = PVE
::Storage
::config
();
1844 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1849 my $service = "vm:$vmid";
1851 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1853 print "Executing HA stop for VM $vmid\n";
1855 PVE
::Tools
::run_command
($cmd);
1860 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1866 syslog
('info', "stop VM $vmid: $upid\n");
1868 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1869 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1874 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1878 __PACKAGE__-
>register_method({
1880 path
=> '{vmid}/status/reset',
1884 description
=> "Reset virtual machine.",
1886 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1889 additionalProperties
=> 0,
1891 node
=> get_standard_option
('pve-node'),
1892 vmid
=> get_standard_option
('pve-vmid',
1893 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1894 skiplock
=> get_standard_option
('skiplock'),
1903 my $rpcenv = PVE
::RPCEnvironment
::get
();
1905 my $authuser = $rpcenv->get_user();
1907 my $node = extract_param
($param, 'node');
1909 my $vmid = extract_param
($param, 'vmid');
1911 my $skiplock = extract_param
($param, 'skiplock');
1912 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1913 if $skiplock && $authuser ne 'root@pam';
1915 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1920 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1925 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1928 __PACKAGE__-
>register_method({
1929 name
=> 'vm_shutdown',
1930 path
=> '{vmid}/status/shutdown',
1934 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1935 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1937 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1940 additionalProperties
=> 0,
1942 node
=> get_standard_option
('pve-node'),
1943 vmid
=> get_standard_option
('pve-vmid',
1944 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1945 skiplock
=> get_standard_option
('skiplock'),
1947 description
=> "Wait maximal timeout seconds.",
1953 description
=> "Make sure the VM stops.",
1959 description
=> "Do not deactivate storage volumes.",
1972 my $rpcenv = PVE
::RPCEnvironment
::get
();
1974 my $authuser = $rpcenv->get_user();
1976 my $node = extract_param
($param, 'node');
1978 my $vmid = extract_param
($param, 'vmid');
1980 my $skiplock = extract_param
($param, 'skiplock');
1981 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1982 if $skiplock && $authuser ne 'root@pam';
1984 my $keepActive = extract_param
($param, 'keepActive');
1985 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1986 if $keepActive && $authuser ne 'root@pam';
1988 my $storecfg = PVE
::Storage
::config
();
1992 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1993 # otherwise, we will infer a shutdown command, but run into the timeout,
1994 # then when the vm is resumed, it will instantly shutdown
1996 # checking the qmp status here to get feedback to the gui/cli/api
1997 # and the status query should not take too long
2000 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2004 if (!$err && $qmpstatus->{status
} eq "paused") {
2005 if ($param->{forceStop
}) {
2006 warn "VM is paused - stop instead of shutdown\n";
2009 die "VM is paused - cannot shutdown\n";
2013 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2014 ($rpcenv->{type
} ne 'ha')) {
2019 my $service = "vm:$vmid";
2021 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2023 print "Executing HA stop for VM $vmid\n";
2025 PVE
::Tools
::run_command
($cmd);
2030 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2037 syslog
('info', "shutdown VM $vmid: $upid\n");
2039 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2040 $shutdown, $param->{forceStop
}, $keepActive);
2045 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2049 __PACKAGE__-
>register_method({
2050 name
=> 'vm_suspend',
2051 path
=> '{vmid}/status/suspend',
2055 description
=> "Suspend virtual machine.",
2057 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2060 additionalProperties
=> 0,
2062 node
=> get_standard_option
('pve-node'),
2063 vmid
=> get_standard_option
('pve-vmid',
2064 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2065 skiplock
=> get_standard_option
('skiplock'),
2074 my $rpcenv = PVE
::RPCEnvironment
::get
();
2076 my $authuser = $rpcenv->get_user();
2078 my $node = extract_param
($param, 'node');
2080 my $vmid = extract_param
($param, 'vmid');
2082 my $skiplock = extract_param
($param, 'skiplock');
2083 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2084 if $skiplock && $authuser ne 'root@pam';
2086 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2091 syslog
('info', "suspend VM $vmid: $upid\n");
2093 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2098 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2101 __PACKAGE__-
>register_method({
2102 name
=> 'vm_resume',
2103 path
=> '{vmid}/status/resume',
2107 description
=> "Resume virtual machine.",
2109 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2112 additionalProperties
=> 0,
2114 node
=> get_standard_option
('pve-node'),
2115 vmid
=> get_standard_option
('pve-vmid',
2116 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2117 skiplock
=> get_standard_option
('skiplock'),
2118 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2128 my $rpcenv = PVE
::RPCEnvironment
::get
();
2130 my $authuser = $rpcenv->get_user();
2132 my $node = extract_param
($param, 'node');
2134 my $vmid = extract_param
($param, 'vmid');
2136 my $skiplock = extract_param
($param, 'skiplock');
2137 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2138 if $skiplock && $authuser ne 'root@pam';
2140 my $nocheck = extract_param
($param, 'nocheck');
2142 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2147 syslog
('info', "resume VM $vmid: $upid\n");
2149 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2154 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2157 __PACKAGE__-
>register_method({
2158 name
=> 'vm_sendkey',
2159 path
=> '{vmid}/sendkey',
2163 description
=> "Send key event to virtual machine.",
2165 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2168 additionalProperties
=> 0,
2170 node
=> get_standard_option
('pve-node'),
2171 vmid
=> get_standard_option
('pve-vmid',
2172 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2173 skiplock
=> get_standard_option
('skiplock'),
2175 description
=> "The key (qemu monitor encoding).",
2180 returns
=> { type
=> 'null'},
2184 my $rpcenv = PVE
::RPCEnvironment
::get
();
2186 my $authuser = $rpcenv->get_user();
2188 my $node = extract_param
($param, 'node');
2190 my $vmid = extract_param
($param, 'vmid');
2192 my $skiplock = extract_param
($param, 'skiplock');
2193 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2194 if $skiplock && $authuser ne 'root@pam';
2196 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2201 __PACKAGE__-
>register_method({
2202 name
=> 'vm_feature',
2203 path
=> '{vmid}/feature',
2207 description
=> "Check if feature for virtual machine is available.",
2209 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2212 additionalProperties
=> 0,
2214 node
=> get_standard_option
('pve-node'),
2215 vmid
=> get_standard_option
('pve-vmid'),
2217 description
=> "Feature to check.",
2219 enum
=> [ 'snapshot', 'clone', 'copy' ],
2221 snapname
=> get_standard_option
('pve-snapshot-name', {
2229 hasFeature
=> { type
=> 'boolean' },
2232 items
=> { type
=> 'string' },
2239 my $node = extract_param
($param, 'node');
2241 my $vmid = extract_param
($param, 'vmid');
2243 my $snapname = extract_param
($param, 'snapname');
2245 my $feature = extract_param
($param, 'feature');
2247 my $running = PVE
::QemuServer
::check_running
($vmid);
2249 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2252 my $snap = $conf->{snapshots
}->{$snapname};
2253 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2256 my $storecfg = PVE
::Storage
::config
();
2258 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2259 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2262 hasFeature
=> $hasFeature,
2263 nodes
=> [ keys %$nodelist ],
2267 __PACKAGE__-
>register_method({
2269 path
=> '{vmid}/clone',
2273 description
=> "Create a copy of virtual machine/template.",
2275 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2276 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2277 "'Datastore.AllocateSpace' on any used storage.",
2280 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2282 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2283 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2288 additionalProperties
=> 0,
2290 node
=> get_standard_option
('pve-node'),
2291 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2292 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2295 type
=> 'string', format
=> 'dns-name',
2296 description
=> "Set a name for the new VM.",
2301 description
=> "Description for the new VM.",
2305 type
=> 'string', format
=> 'pve-poolid',
2306 description
=> "Add the new VM to the specified pool.",
2308 snapname
=> get_standard_option
('pve-snapshot-name', {
2311 storage
=> get_standard_option
('pve-storage-id', {
2312 description
=> "Target storage for full clone.",
2317 description
=> "Target format for file storage.",
2321 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2326 description
=> "Create a full copy of all disk. This is always done when " .
2327 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2330 target
=> get_standard_option
('pve-node', {
2331 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2342 my $rpcenv = PVE
::RPCEnvironment
::get
();
2344 my $authuser = $rpcenv->get_user();
2346 my $node = extract_param
($param, 'node');
2348 my $vmid = extract_param
($param, 'vmid');
2350 my $newid = extract_param
($param, 'newid');
2352 my $pool = extract_param
($param, 'pool');
2354 if (defined($pool)) {
2355 $rpcenv->check_pool_exist($pool);
2358 my $snapname = extract_param
($param, 'snapname');
2360 my $storage = extract_param
($param, 'storage');
2362 my $format = extract_param
($param, 'format');
2364 my $target = extract_param
($param, 'target');
2366 my $localnode = PVE
::INotify
::nodename
();
2368 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2370 PVE
::Cluster
::check_node_exists
($target) if $target;
2372 my $storecfg = PVE
::Storage
::config
();
2375 # check if storage is enabled on local node
2376 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2378 # check if storage is available on target node
2379 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2380 # clone only works if target storage is shared
2381 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2382 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2386 PVE
::Cluster
::check_cfs_quorum
();
2388 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2390 # exclusive lock if VM is running - else shared lock is enough;
2391 my $shared_lock = $running ?
0 : 1;
2395 # do all tests after lock
2396 # we also try to do all tests before we fork the worker
2398 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2400 PVE
::QemuConfig-
>check_lock($conf);
2402 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2404 die "unexpected state change\n" if $verify_running != $running;
2406 die "snapshot '$snapname' does not exist\n"
2407 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2409 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2411 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2413 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2415 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2417 die "unable to create VM $newid: config file already exists\n"
2420 my $newconf = { lock => 'clone' };
2425 foreach my $opt (keys %$oldconf) {
2426 my $value = $oldconf->{$opt};
2428 # do not copy snapshot related info
2429 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2430 $opt eq 'vmstate' || $opt eq 'snapstate';
2432 # no need to copy unused images, because VMID(owner) changes anyways
2433 next if $opt =~ m/^unused\d+$/;
2435 # always change MAC! address
2436 if ($opt =~ m/^net(\d+)$/) {
2437 my $net = PVE
::QemuServer
::parse_net
($value);
2438 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2439 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2440 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2441 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2442 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2443 die "unable to parse drive options for '$opt'\n" if !$drive;
2444 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2445 $newconf->{$opt} = $value; # simply copy configuration
2447 if ($param->{full
}) {
2448 die "Full clone feature is not available"
2449 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2450 $fullclone->{$opt} = 1;
2452 # not full means clone instead of copy
2453 die "Linked clone feature is not available"
2454 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2456 $drives->{$opt} = $drive;
2457 push @$vollist, $drive->{file
};
2460 # copy everything else
2461 $newconf->{$opt} = $value;
2465 # auto generate a new uuid
2466 my ($uuid, $uuid_str);
2467 UUID
::generate
($uuid);
2468 UUID
::unparse
($uuid, $uuid_str);
2469 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2470 $smbios1->{uuid
} = $uuid_str;
2471 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2473 delete $newconf->{template
};
2475 if ($param->{name
}) {
2476 $newconf->{name
} = $param->{name
};
2478 if ($oldconf->{name
}) {
2479 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2481 $newconf->{name
} = "Copy-of-VM-$vmid";
2485 if ($param->{description
}) {
2486 $newconf->{description
} = $param->{description
};
2489 # create empty/temp config - this fails if VM already exists on other node
2490 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2495 my $newvollist = [];
2499 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2501 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2503 my $total_jobs = scalar(keys %{$drives});
2506 foreach my $opt (keys %$drives) {
2507 my $drive = $drives->{$opt};
2508 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2510 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2511 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2512 $jobs, $skipcomplete, $oldconf->{agent
});
2514 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2516 PVE
::QemuConfig-
>write_config($newid, $newconf);
2520 delete $newconf->{lock};
2521 PVE
::QemuConfig-
>write_config($newid, $newconf);
2524 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2525 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2526 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2528 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2529 die "Failed to move config to node '$target' - rename failed: $!\n"
2530 if !rename($conffile, $newconffile);
2533 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2538 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2540 sleep 1; # some storage like rbd need to wait before release volume - really?
2542 foreach my $volid (@$newvollist) {
2543 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2546 die "clone failed: $err";
2552 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2554 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2557 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2558 # Aquire exclusive lock lock for $newid
2559 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2564 __PACKAGE__-
>register_method({
2565 name
=> 'move_vm_disk',
2566 path
=> '{vmid}/move_disk',
2570 description
=> "Move volume to different storage.",
2572 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2574 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2575 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2579 additionalProperties
=> 0,
2581 node
=> get_standard_option
('pve-node'),
2582 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2585 description
=> "The disk you want to move.",
2586 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2588 storage
=> get_standard_option
('pve-storage-id', {
2589 description
=> "Target storage.",
2590 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2594 description
=> "Target Format.",
2595 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2600 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2606 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2614 description
=> "the task ID.",
2619 my $rpcenv = PVE
::RPCEnvironment
::get
();
2621 my $authuser = $rpcenv->get_user();
2623 my $node = extract_param
($param, 'node');
2625 my $vmid = extract_param
($param, 'vmid');
2627 my $digest = extract_param
($param, 'digest');
2629 my $disk = extract_param
($param, 'disk');
2631 my $storeid = extract_param
($param, 'storage');
2633 my $format = extract_param
($param, 'format');
2635 my $storecfg = PVE
::Storage
::config
();
2637 my $updatefn = sub {
2639 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2641 PVE
::QemuConfig-
>check_lock($conf);
2643 die "checksum missmatch (file change by other user?)\n"
2644 if $digest && $digest ne $conf->{digest
};
2646 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2648 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2650 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2652 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2655 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2656 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2660 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2661 (!$format || !$oldfmt || $oldfmt eq $format);
2663 # this only checks snapshots because $disk is passed!
2664 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2665 die "you can't move a disk with snapshots and delete the source\n"
2666 if $snapshotted && $param->{delete};
2668 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2670 my $running = PVE
::QemuServer
::check_running
($vmid);
2672 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2676 my $newvollist = [];
2679 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2681 warn "moving disk with snapshots, snapshots will not be moved!\n"
2684 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2685 $vmid, $storeid, $format, 1, $newvollist);
2687 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2689 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2691 # convert moved disk to base if part of template
2692 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2693 if PVE
::QemuConfig-
>is_template($conf);
2695 PVE
::QemuConfig-
>write_config($vmid, $conf);
2698 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2699 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2706 foreach my $volid (@$newvollist) {
2707 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2710 die "storage migration failed: $err";
2713 if ($param->{delete}) {
2715 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2716 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2722 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2725 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2728 __PACKAGE__-
>register_method({
2729 name
=> 'migrate_vm',
2730 path
=> '{vmid}/migrate',
2734 description
=> "Migrate virtual machine. Creates a new migration task.",
2736 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2739 additionalProperties
=> 0,
2741 node
=> get_standard_option
('pve-node'),
2742 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2743 target
=> get_standard_option
('pve-node', {
2744 description
=> "Target node.",
2745 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2749 description
=> "Use online/live migration.",
2754 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2759 enum
=> ['secure', 'insecure'],
2760 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2763 migration_network
=> {
2764 type
=> 'string', format
=> 'CIDR',
2765 description
=> "CIDR of the (sub) network that is used for migration.",
2768 "with-local-disks" => {
2770 description
=> "Enable live storage migration for local disk",
2773 targetstorage
=> get_standard_option
('pve-storage-id', {
2774 description
=> "Default target storage.",
2776 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2782 description
=> "the task ID.",
2787 my $rpcenv = PVE
::RPCEnvironment
::get
();
2789 my $authuser = $rpcenv->get_user();
2791 my $target = extract_param
($param, 'target');
2793 my $localnode = PVE
::INotify
::nodename
();
2794 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2796 PVE
::Cluster
::check_cfs_quorum
();
2798 PVE
::Cluster
::check_node_exists
($target);
2800 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2802 my $vmid = extract_param
($param, 'vmid');
2804 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2805 if !$param->{online
} && $param->{targetstorage
};
2807 raise_param_exc
({ force
=> "Only root may use this option." })
2808 if $param->{force
} && $authuser ne 'root@pam';
2810 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2811 if $param->{migration_type
} && $authuser ne 'root@pam';
2813 # allow root only until better network permissions are available
2814 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2815 if $param->{migration_network
} && $authuser ne 'root@pam';
2818 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2820 # try to detect errors early
2822 PVE
::QemuConfig-
>check_lock($conf);
2824 if (PVE
::QemuServer
::check_running
($vmid)) {
2825 die "cant migrate running VM without --online\n"
2826 if !$param->{online
};
2829 my $storecfg = PVE
::Storage
::config
();
2831 if( $param->{targetstorage
}) {
2832 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2834 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2837 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2842 my $service = "vm:$vmid";
2844 my $cmd = ['ha-manager', 'migrate', $service, $target];
2846 print "Executing HA migrate for VM $vmid to node $target\n";
2848 PVE
::Tools
::run_command
($cmd);
2853 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2860 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2863 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2868 __PACKAGE__-
>register_method({
2870 path
=> '{vmid}/monitor',
2874 description
=> "Execute Qemu monitor commands.",
2876 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2877 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2880 additionalProperties
=> 0,
2882 node
=> get_standard_option
('pve-node'),
2883 vmid
=> get_standard_option
('pve-vmid'),
2886 description
=> "The monitor command.",
2890 returns
=> { type
=> 'string'},
2894 my $rpcenv = PVE
::RPCEnvironment
::get
();
2895 my $authuser = $rpcenv->get_user();
2898 my $command = shift;
2899 return $command =~ m/^\s*info(\s+|$)/
2900 || $command =~ m/^\s*help\s*$/;
2903 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2904 if !&$is_ro($param->{command
});
2906 my $vmid = $param->{vmid
};
2908 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2912 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2914 $res = "ERROR: $@" if $@;
2919 my $guest_agent_commands = [
2927 'network-get-interfaces',
2930 'get-memory-blocks',
2931 'get-memory-block-info',
2938 __PACKAGE__-
>register_method({
2940 path
=> '{vmid}/agent',
2944 description
=> "Execute Qemu Guest Agent commands.",
2946 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2949 additionalProperties
=> 0,
2951 node
=> get_standard_option
('pve-node'),
2952 vmid
=> get_standard_option
('pve-vmid', {
2953 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2956 description
=> "The QGA command.",
2957 enum
=> $guest_agent_commands,
2963 description
=> "Returns an object with a single `result` property. The type of that
2964 property depends on the executed command.",
2969 my $vmid = $param->{vmid
};
2971 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2973 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2974 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2976 my $cmd = $param->{command
};
2978 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2980 return { result
=> $res };
2983 __PACKAGE__-
>register_method({
2984 name
=> 'resize_vm',
2985 path
=> '{vmid}/resize',
2989 description
=> "Extend volume size.",
2991 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2994 additionalProperties
=> 0,
2996 node
=> get_standard_option
('pve-node'),
2997 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2998 skiplock
=> get_standard_option
('skiplock'),
3001 description
=> "The disk you want to resize.",
3002 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3006 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3007 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.",
3011 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3017 returns
=> { type
=> 'null'},
3021 my $rpcenv = PVE
::RPCEnvironment
::get
();
3023 my $authuser = $rpcenv->get_user();
3025 my $node = extract_param
($param, 'node');
3027 my $vmid = extract_param
($param, 'vmid');
3029 my $digest = extract_param
($param, 'digest');
3031 my $disk = extract_param
($param, 'disk');
3033 my $sizestr = extract_param
($param, 'size');
3035 my $skiplock = extract_param
($param, 'skiplock');
3036 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3037 if $skiplock && $authuser ne 'root@pam';
3039 my $storecfg = PVE
::Storage
::config
();
3041 my $updatefn = sub {
3043 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3045 die "checksum missmatch (file change by other user?)\n"
3046 if $digest && $digest ne $conf->{digest
};
3047 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3049 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3051 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3053 my (undef, undef, undef, undef, undef, undef, $format) =
3054 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3056 die "can't resize volume: $disk if snapshot exists\n"
3057 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3059 my $volid = $drive->{file
};
3061 die "disk '$disk' has no associated volume\n" if !$volid;
3063 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3065 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3067 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3069 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3070 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3072 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3073 my ($ext, $newsize, $unit) = ($1, $2, $4);
3076 $newsize = $newsize * 1024;
3077 } elsif ($unit eq 'M') {
3078 $newsize = $newsize * 1024 * 1024;
3079 } elsif ($unit eq 'G') {
3080 $newsize = $newsize * 1024 * 1024 * 1024;
3081 } elsif ($unit eq 'T') {
3082 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3085 $newsize += $size if $ext;
3086 $newsize = int($newsize);
3088 die "shrinking disks is not supported\n" if $newsize < $size;
3090 return if $size == $newsize;
3092 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3094 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3096 $drive->{size
} = $newsize;
3097 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3099 PVE
::QemuConfig-
>write_config($vmid, $conf);
3102 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3106 __PACKAGE__-
>register_method({
3107 name
=> 'snapshot_list',
3108 path
=> '{vmid}/snapshot',
3110 description
=> "List all snapshots.",
3112 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3115 protected
=> 1, # qemu pid files are only readable by root
3117 additionalProperties
=> 0,
3119 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3120 node
=> get_standard_option
('pve-node'),
3129 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3134 my $vmid = $param->{vmid
};
3136 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3137 my $snaphash = $conf->{snapshots
} || {};
3141 foreach my $name (keys %$snaphash) {
3142 my $d = $snaphash->{$name};
3145 snaptime
=> $d->{snaptime
} || 0,
3146 vmstate
=> $d->{vmstate
} ?
1 : 0,
3147 description
=> $d->{description
} || '',
3149 $item->{parent
} = $d->{parent
} if $d->{parent
};
3150 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3154 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3155 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3156 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3158 push @$res, $current;
3163 __PACKAGE__-
>register_method({
3165 path
=> '{vmid}/snapshot',
3169 description
=> "Snapshot a VM.",
3171 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3174 additionalProperties
=> 0,
3176 node
=> get_standard_option
('pve-node'),
3177 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3178 snapname
=> get_standard_option
('pve-snapshot-name'),
3182 description
=> "Save the vmstate",
3187 description
=> "A textual description or comment.",
3193 description
=> "the task ID.",
3198 my $rpcenv = PVE
::RPCEnvironment
::get
();
3200 my $authuser = $rpcenv->get_user();
3202 my $node = extract_param
($param, 'node');
3204 my $vmid = extract_param
($param, 'vmid');
3206 my $snapname = extract_param
($param, 'snapname');
3208 die "unable to use snapshot name 'current' (reserved name)\n"
3209 if $snapname eq 'current';
3212 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3213 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3214 $param->{description
});
3217 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3220 __PACKAGE__-
>register_method({
3221 name
=> 'snapshot_cmd_idx',
3222 path
=> '{vmid}/snapshot/{snapname}',
3229 additionalProperties
=> 0,
3231 vmid
=> get_standard_option
('pve-vmid'),
3232 node
=> get_standard_option
('pve-node'),
3233 snapname
=> get_standard_option
('pve-snapshot-name'),
3242 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3249 push @$res, { cmd
=> 'rollback' };
3250 push @$res, { cmd
=> 'config' };
3255 __PACKAGE__-
>register_method({
3256 name
=> 'update_snapshot_config',
3257 path
=> '{vmid}/snapshot/{snapname}/config',
3261 description
=> "Update snapshot metadata.",
3263 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3266 additionalProperties
=> 0,
3268 node
=> get_standard_option
('pve-node'),
3269 vmid
=> get_standard_option
('pve-vmid'),
3270 snapname
=> get_standard_option
('pve-snapshot-name'),
3274 description
=> "A textual description or comment.",
3278 returns
=> { type
=> 'null' },
3282 my $rpcenv = PVE
::RPCEnvironment
::get
();
3284 my $authuser = $rpcenv->get_user();
3286 my $vmid = extract_param
($param, 'vmid');
3288 my $snapname = extract_param
($param, 'snapname');
3290 return undef if !defined($param->{description
});
3292 my $updatefn = sub {
3294 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3296 PVE
::QemuConfig-
>check_lock($conf);
3298 my $snap = $conf->{snapshots
}->{$snapname};
3300 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3302 $snap->{description
} = $param->{description
} if defined($param->{description
});
3304 PVE
::QemuConfig-
>write_config($vmid, $conf);
3307 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3312 __PACKAGE__-
>register_method({
3313 name
=> 'get_snapshot_config',
3314 path
=> '{vmid}/snapshot/{snapname}/config',
3317 description
=> "Get snapshot configuration",
3319 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3322 additionalProperties
=> 0,
3324 node
=> get_standard_option
('pve-node'),
3325 vmid
=> get_standard_option
('pve-vmid'),
3326 snapname
=> get_standard_option
('pve-snapshot-name'),
3329 returns
=> { type
=> "object" },
3333 my $rpcenv = PVE
::RPCEnvironment
::get
();
3335 my $authuser = $rpcenv->get_user();
3337 my $vmid = extract_param
($param, 'vmid');
3339 my $snapname = extract_param
($param, 'snapname');
3341 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3343 my $snap = $conf->{snapshots
}->{$snapname};
3345 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3350 __PACKAGE__-
>register_method({
3352 path
=> '{vmid}/snapshot/{snapname}/rollback',
3356 description
=> "Rollback VM state to specified snapshot.",
3358 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3361 additionalProperties
=> 0,
3363 node
=> get_standard_option
('pve-node'),
3364 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3365 snapname
=> get_standard_option
('pve-snapshot-name'),
3370 description
=> "the task ID.",
3375 my $rpcenv = PVE
::RPCEnvironment
::get
();
3377 my $authuser = $rpcenv->get_user();
3379 my $node = extract_param
($param, 'node');
3381 my $vmid = extract_param
($param, 'vmid');
3383 my $snapname = extract_param
($param, 'snapname');
3386 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3387 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3390 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3393 __PACKAGE__-
>register_method({
3394 name
=> 'delsnapshot',
3395 path
=> '{vmid}/snapshot/{snapname}',
3399 description
=> "Delete a VM snapshot.",
3401 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3404 additionalProperties
=> 0,
3406 node
=> get_standard_option
('pve-node'),
3407 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3408 snapname
=> get_standard_option
('pve-snapshot-name'),
3412 description
=> "For removal from config file, even if removing disk snapshots fails.",
3418 description
=> "the task ID.",
3423 my $rpcenv = PVE
::RPCEnvironment
::get
();
3425 my $authuser = $rpcenv->get_user();
3427 my $node = extract_param
($param, 'node');
3429 my $vmid = extract_param
($param, 'vmid');
3431 my $snapname = extract_param
($param, 'snapname');
3434 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3435 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3438 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3441 __PACKAGE__-
>register_method({
3443 path
=> '{vmid}/template',
3447 description
=> "Create a Template.",
3449 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3450 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3453 additionalProperties
=> 0,
3455 node
=> get_standard_option
('pve-node'),
3456 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3460 description
=> "If you want to convert only 1 disk to base image.",
3461 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3466 returns
=> { type
=> 'null'},
3470 my $rpcenv = PVE
::RPCEnvironment
::get
();
3472 my $authuser = $rpcenv->get_user();
3474 my $node = extract_param
($param, 'node');
3476 my $vmid = extract_param
($param, 'vmid');
3478 my $disk = extract_param
($param, 'disk');
3480 my $updatefn = sub {
3482 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3484 PVE
::QemuConfig-
>check_lock($conf);
3486 die "unable to create template, because VM contains snapshots\n"
3487 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3489 die "you can't convert a template to a template\n"
3490 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3492 die "you can't convert a VM to template if VM is running\n"
3493 if PVE
::QemuServer
::check_running
($vmid);
3496 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3499 $conf->{template
} = 1;
3500 PVE
::QemuConfig-
>write_config($vmid, $conf);
3502 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3505 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);