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
;
19 use PVE
::GuestHelpers
;
23 use PVE
::RPCEnvironment
;
24 use PVE
::AccessControl
;
28 use PVE
::API2
::Firewall
::VM
;
31 if (!$ENV{PVE_GENERATING_DOCS
}) {
32 require PVE
::HA
::Env
::PVE2
;
33 import PVE
::HA
::Env
::PVE2
;
34 require PVE
::HA
::Config
;
35 import PVE
::HA
::Config
;
39 use Data
::Dumper
; # fixme: remove
41 use base
qw(PVE::RESTHandler);
43 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.";
45 my $resolve_cdrom_alias = sub {
48 if (my $value = $param->{cdrom
}) {
49 $value .= ",media=cdrom" if $value !~ m/media=/;
50 $param->{ide2
} = $value;
51 delete $param->{cdrom
};
55 my $check_storage_access = sub {
56 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
58 PVE
::QemuServer
::foreach_drive
($settings, sub {
59 my ($ds, $drive) = @_;
61 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
63 my $volid = $drive->{file
};
65 if (!$volid || $volid eq 'none') {
67 } elsif ($isCDROM && ($volid eq 'cdrom')) {
68 $rpcenv->check($authuser, "/", ['Sys.Console']);
69 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
70 my ($storeid, $size) = ($2 || $default_storage, $3);
71 die "no storage ID specified (and no default storage)\n" if !$storeid;
72 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
74 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
79 my $check_storage_access_clone = sub {
80 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
84 PVE
::QemuServer
::foreach_drive
($conf, sub {
85 my ($ds, $drive) = @_;
87 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
89 my $volid = $drive->{file
};
91 return if !$volid || $volid eq 'none';
94 if ($volid eq 'cdrom') {
95 $rpcenv->check($authuser, "/", ['Sys.Console']);
97 # we simply allow access
98 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
99 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
100 $sharedvm = 0 if !$scfg->{shared
};
104 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
105 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
106 $sharedvm = 0 if !$scfg->{shared
};
108 $sid = $storage if $storage;
109 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
116 # Note: $pool is only needed when creating a VM, because pool permissions
117 # are automatically inherited if VM already exists inside a pool.
118 my $create_disks = sub {
119 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
126 my ($ds, $disk) = @_;
128 my $volid = $disk->{file
};
130 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
131 delete $disk->{size
};
132 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
133 } elsif ($volid =~ m!^(([^/:\s]+):)?(\d+(\.\d+)?)$!) {
134 my ($storeid, $size) = ($2 || $default_storage, $3);
135 die "no storage ID specified (and no default storage)\n" if !$storeid;
136 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
137 my $fmt = $disk->{format
} || $defformat;
140 if ($ds eq 'efidisk0') {
142 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
143 die "uefi vars image not found\n" if ! -f
$ovmfvars;
144 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
146 $disk->{file
} = $volid;
147 $disk->{size
} = 128*1024;
148 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
149 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
150 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
151 my $path = PVE
::Storage
::path
($storecfg, $volid);
152 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
153 push @$efidiskcmd, $ovmfvars;
154 push @$efidiskcmd, $path;
156 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
158 eval { PVE
::Tools
::run_command
($efidiskcmd); };
160 die "Copying of EFI Vars image failed: $err" if $err;
162 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
163 $fmt, undef, $size*1024*1024);
164 $disk->{file
} = $volid;
165 $disk->{size
} = $size*1024*1024*1024;
167 push @$vollist, $volid;
168 delete $disk->{format
}; # no longer needed
169 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
172 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
174 my $volid_is_new = 1;
177 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
178 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
183 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
185 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
187 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
189 die "volume $volid does not exists\n" if !$size;
191 $disk->{size
} = $size;
194 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
198 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
200 # free allocated images on error
202 syslog
('err', "VM $vmid creating disks failed");
203 foreach my $volid (@$vollist) {
204 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
210 # modify vm config if everything went well
211 foreach my $ds (keys %$res) {
212 $conf->{$ds} = $res->{$ds};
229 my $memoryoptions = {
235 my $hwtypeoptions = {
247 my $generaloptions = {
254 'migrate_downtime' => 1,
255 'migrate_speed' => 1,
267 my $vmpoweroptions = {
276 my $check_vm_modify_config_perm = sub {
277 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
279 return 1 if $authuser eq 'root@pam';
281 foreach my $opt (@$key_list) {
282 # disk checks need to be done somewhere else
283 next if PVE
::QemuServer
::is_valid_drivename
($opt);
284 next if $opt eq 'cdrom';
285 next if $opt =~ m/^unused\d+$/;
287 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
289 } elsif ($memoryoptions->{$opt}) {
290 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
291 } elsif ($hwtypeoptions->{$opt}) {
292 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
293 } elsif ($generaloptions->{$opt}) {
294 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
295 # special case for startup since it changes host behaviour
296 if ($opt eq 'startup') {
297 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
299 } elsif ($vmpoweroptions->{$opt}) {
300 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
301 } elsif ($diskoptions->{$opt}) {
302 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
303 } elsif ($opt =~ m/^net\d+$/) {
304 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
306 # catches usb\d+, hostpci\d+, args, lock, etc.
307 # new options will be checked here
308 die "only root can set '$opt' config\n";
315 __PACKAGE__-
>register_method({
319 description
=> "Virtual machine index (per node).",
321 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
325 protected
=> 1, # qemu pid files are only readable by root
327 additionalProperties
=> 0,
329 node
=> get_standard_option
('pve-node'),
333 description
=> "Determine the full status of active VMs.",
343 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
348 my $rpcenv = PVE
::RPCEnvironment
::get
();
349 my $authuser = $rpcenv->get_user();
351 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
354 foreach my $vmid (keys %$vmstatus) {
355 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
357 my $data = $vmstatus->{$vmid};
358 $data->{vmid
} = int($vmid);
367 __PACKAGE__-
>register_method({
371 description
=> "Create or restore a virtual machine.",
373 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
374 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
375 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
376 user
=> 'all', # check inside
381 additionalProperties
=> 0,
382 properties
=> PVE
::QemuServer
::json_config_properties
(
384 node
=> get_standard_option
('pve-node'),
385 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
387 description
=> "The backup file.",
391 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
393 storage
=> get_standard_option
('pve-storage-id', {
394 description
=> "Default storage.",
396 completion
=> \
&PVE
::QemuServer
::complete_storage
,
401 description
=> "Allow to overwrite existing VM.",
402 requires
=> 'archive',
407 description
=> "Assign a unique random ethernet address.",
408 requires
=> 'archive',
412 type
=> 'string', format
=> 'pve-poolid',
413 description
=> "Add the VM to the specified pool.",
423 my $rpcenv = PVE
::RPCEnvironment
::get
();
425 my $authuser = $rpcenv->get_user();
427 my $node = extract_param
($param, 'node');
429 my $vmid = extract_param
($param, 'vmid');
431 my $archive = extract_param
($param, 'archive');
433 my $storage = extract_param
($param, 'storage');
435 my $force = extract_param
($param, 'force');
437 my $unique = extract_param
($param, 'unique');
439 my $pool = extract_param
($param, 'pool');
441 my $filename = PVE
::QemuConfig-
>config_file($vmid);
443 my $storecfg = PVE
::Storage
::config
();
445 PVE
::Cluster
::check_cfs_quorum
();
447 if (defined($pool)) {
448 $rpcenv->check_pool_exist($pool);
451 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
452 if defined($storage);
454 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
456 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
458 } elsif ($archive && $force && (-f
$filename) &&
459 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
460 # OK: user has VM.Backup permissions, and want to restore an existing VM
466 &$resolve_cdrom_alias($param);
468 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
470 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
472 foreach my $opt (keys %$param) {
473 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
474 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
475 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
477 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
478 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
482 PVE
::QemuServer
::add_random_macs
($param);
484 my $keystr = join(' ', keys %$param);
485 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
487 if ($archive eq '-') {
488 die "pipe requires cli environment\n"
489 if $rpcenv->{type
} ne 'cli';
491 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
492 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
496 my $restorefn = sub {
497 my $vmlist = PVE
::Cluster
::get_vmlist
();
498 if ($vmlist->{ids
}->{$vmid}) {
499 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
500 if ($current_node eq $node) {
501 my $conf = PVE
::QemuConfig-
>load_config($vmid);
503 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
505 die "unable to restore vm $vmid - config file already exists\n"
508 die "unable to restore vm $vmid - vm is running\n"
509 if PVE
::QemuServer
::check_running
($vmid);
511 die "unable to restore vm $vmid - vm is a template\n"
512 if PVE
::QemuConfig-
>is_template($conf);
515 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
520 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
523 unique
=> $unique });
525 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
528 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
534 PVE
::Cluster
::check_vmid_unused
($vmid);
544 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
546 # try to be smart about bootdisk
547 my @disks = PVE
::QemuServer
::valid_drive_names
();
549 foreach my $ds (reverse @disks) {
550 next if !$conf->{$ds};
551 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
552 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
556 if (!$conf->{bootdisk
} && $firstdisk) {
557 $conf->{bootdisk
} = $firstdisk;
560 # auto generate uuid if user did not specify smbios1 option
561 if (!$conf->{smbios1
}) {
562 my ($uuid, $uuid_str);
563 UUID
::generate
($uuid);
564 UUID
::unparse
($uuid, $uuid_str);
565 $conf->{smbios1
} = "uuid=$uuid_str";
568 PVE
::QemuConfig-
>write_config($vmid, $conf);
574 foreach my $volid (@$vollist) {
575 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
578 die "create failed - $err";
581 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
584 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
587 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
590 __PACKAGE__-
>register_method({
595 description
=> "Directory index",
600 additionalProperties
=> 0,
602 node
=> get_standard_option
('pve-node'),
603 vmid
=> get_standard_option
('pve-vmid'),
611 subdir
=> { type
=> 'string' },
614 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
620 { subdir
=> 'config' },
621 { subdir
=> 'pending' },
622 { subdir
=> 'status' },
623 { subdir
=> 'unlink' },
624 { subdir
=> 'vncproxy' },
625 { subdir
=> 'migrate' },
626 { subdir
=> 'resize' },
627 { subdir
=> 'move' },
629 { subdir
=> 'rrddata' },
630 { subdir
=> 'monitor' },
631 { subdir
=> 'agent' },
632 { subdir
=> 'snapshot' },
633 { subdir
=> 'spiceproxy' },
634 { subdir
=> 'sendkey' },
635 { subdir
=> 'firewall' },
641 __PACKAGE__-
>register_method ({
642 subclass
=> "PVE::API2::Firewall::VM",
643 path
=> '{vmid}/firewall',
646 __PACKAGE__-
>register_method({
648 path
=> '{vmid}/rrd',
650 protected
=> 1, # fixme: can we avoid that?
652 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
654 description
=> "Read VM RRD statistics (returns PNG)",
656 additionalProperties
=> 0,
658 node
=> get_standard_option
('pve-node'),
659 vmid
=> get_standard_option
('pve-vmid'),
661 description
=> "Specify the time frame you are interested in.",
663 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
666 description
=> "The list of datasources you want to display.",
667 type
=> 'string', format
=> 'pve-configid-list',
670 description
=> "The RRD consolidation function",
672 enum
=> [ 'AVERAGE', 'MAX' ],
680 filename
=> { type
=> 'string' },
686 return PVE
::Cluster
::create_rrd_graph
(
687 "pve2-vm/$param->{vmid}", $param->{timeframe
},
688 $param->{ds
}, $param->{cf
});
692 __PACKAGE__-
>register_method({
694 path
=> '{vmid}/rrddata',
696 protected
=> 1, # fixme: can we avoid that?
698 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
700 description
=> "Read VM RRD statistics",
702 additionalProperties
=> 0,
704 node
=> get_standard_option
('pve-node'),
705 vmid
=> get_standard_option
('pve-vmid'),
707 description
=> "Specify the time frame you are interested in.",
709 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
712 description
=> "The RRD consolidation function",
714 enum
=> [ 'AVERAGE', 'MAX' ],
729 return PVE
::Cluster
::create_rrd_data
(
730 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
734 __PACKAGE__-
>register_method({
736 path
=> '{vmid}/config',
739 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
744 additionalProperties
=> 0,
746 node
=> get_standard_option
('pve-node'),
747 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
749 description
=> "Get current values (instead of pending values).",
761 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
768 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
770 delete $conf->{snapshots
};
772 if (!$param->{current
}) {
773 foreach my $opt (keys %{$conf->{pending
}}) {
774 next if $opt eq 'delete';
775 my $value = $conf->{pending
}->{$opt};
776 next if ref($value); # just to be sure
777 $conf->{$opt} = $value;
779 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
780 foreach my $opt (keys %$pending_delete_hash) {
781 delete $conf->{$opt} if $conf->{$opt};
785 delete $conf->{pending
};
790 __PACKAGE__-
>register_method({
791 name
=> 'vm_pending',
792 path
=> '{vmid}/pending',
795 description
=> "Get virtual machine configuration, including pending changes.",
797 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
800 additionalProperties
=> 0,
802 node
=> get_standard_option
('pve-node'),
803 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
812 description
=> "Configuration option name.",
816 description
=> "Current value.",
821 description
=> "Pending value.",
826 description
=> "Indicates a pending delete request if present and not 0. " .
827 "The value 2 indicates a force-delete request.",
839 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
841 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
845 foreach my $opt (keys %$conf) {
846 next if ref($conf->{$opt});
847 my $item = { key
=> $opt };
848 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
849 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
850 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
854 foreach my $opt (keys %{$conf->{pending
}}) {
855 next if $opt eq 'delete';
856 next if ref($conf->{pending
}->{$opt}); # just to be sure
857 next if defined($conf->{$opt});
858 my $item = { key
=> $opt };
859 $item->{pending
} = $conf->{pending
}->{$opt};
863 while (my ($opt, $force) = each %$pending_delete_hash) {
864 next if $conf->{pending
}->{$opt}; # just to be sure
865 next if $conf->{$opt};
866 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
873 # POST/PUT {vmid}/config implementation
875 # The original API used PUT (idempotent) an we assumed that all operations
876 # are fast. But it turned out that almost any configuration change can
877 # involve hot-plug actions, or disk alloc/free. Such actions can take long
878 # time to complete and have side effects (not idempotent).
880 # The new implementation uses POST and forks a worker process. We added
881 # a new option 'background_delay'. If specified we wait up to
882 # 'background_delay' second for the worker task to complete. It returns null
883 # if the task is finished within that time, else we return the UPID.
885 my $update_vm_api = sub {
886 my ($param, $sync) = @_;
888 my $rpcenv = PVE
::RPCEnvironment
::get
();
890 my $authuser = $rpcenv->get_user();
892 my $node = extract_param
($param, 'node');
894 my $vmid = extract_param
($param, 'vmid');
896 my $digest = extract_param
($param, 'digest');
898 my $background_delay = extract_param
($param, 'background_delay');
900 my @paramarr = (); # used for log message
901 foreach my $key (keys %$param) {
902 push @paramarr, "-$key", $param->{$key};
905 my $skiplock = extract_param
($param, 'skiplock');
906 raise_param_exc
({ skiplock
=> "Only root may use this option." })
907 if $skiplock && $authuser ne 'root@pam';
909 my $delete_str = extract_param
($param, 'delete');
911 my $revert_str = extract_param
($param, 'revert');
913 my $force = extract_param
($param, 'force');
915 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
917 my $storecfg = PVE
::Storage
::config
();
919 my $defaults = PVE
::QemuServer
::load_defaults
();
921 &$resolve_cdrom_alias($param);
923 # now try to verify all parameters
926 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
927 if (!PVE
::QemuServer
::option_exists
($opt)) {
928 raise_param_exc
({ revert
=> "unknown option '$opt'" });
931 raise_param_exc
({ delete => "you can't use '-$opt' and " .
932 "-revert $opt' at the same time" })
933 if defined($param->{$opt});
939 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
940 $opt = 'ide2' if $opt eq 'cdrom';
942 raise_param_exc
({ delete => "you can't use '-$opt' and " .
943 "-delete $opt' at the same time" })
944 if defined($param->{$opt});
946 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
947 "-revert $opt' at the same time" })
950 if (!PVE
::QemuServer
::option_exists
($opt)) {
951 raise_param_exc
({ delete => "unknown option '$opt'" });
957 foreach my $opt (keys %$param) {
958 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
960 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
961 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
962 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
963 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
964 } elsif ($opt =~ m/^net(\d+)$/) {
966 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
967 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
971 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
973 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
975 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
979 my $conf = PVE
::QemuConfig-
>load_config($vmid);
981 die "checksum missmatch (file change by other user?)\n"
982 if $digest && $digest ne $conf->{digest
};
984 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
986 foreach my $opt (keys %$revert) {
987 if (defined($conf->{$opt})) {
988 $param->{$opt} = $conf->{$opt};
989 } elsif (defined($conf->{pending
}->{$opt})) {
994 if ($param->{memory
} || defined($param->{balloon
})) {
995 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
996 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
998 die "balloon value too large (must be smaller than assigned memory)\n"
999 if $balloon && $balloon > $maxmem;
1002 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1006 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1008 # write updates to pending section
1010 my $modified = {}; # record what $option we modify
1012 foreach my $opt (@delete) {
1013 $modified->{$opt} = 1;
1014 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1015 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1016 warn "cannot delete '$opt' - not set in current configuration!\n";
1017 $modified->{$opt} = 0;
1021 if ($opt =~ m/^unused/) {
1022 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1023 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1024 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1025 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1026 delete $conf->{$opt};
1027 PVE
::QemuConfig-
>write_config($vmid, $conf);
1029 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1030 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1031 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1032 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1033 if defined($conf->{pending
}->{$opt});
1034 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1035 PVE
::QemuConfig-
>write_config($vmid, $conf);
1037 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1038 PVE
::QemuConfig-
>write_config($vmid, $conf);
1042 foreach my $opt (keys %$param) { # add/change
1043 $modified->{$opt} = 1;
1044 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1045 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1047 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1048 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1049 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1050 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1052 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1054 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1055 if defined($conf->{pending
}->{$opt});
1057 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1059 $conf->{pending
}->{$opt} = $param->{$opt};
1061 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1062 PVE
::QemuConfig-
>write_config($vmid, $conf);
1065 # remove pending changes when nothing changed
1066 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1067 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1068 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1070 return if !scalar(keys %{$conf->{pending
}});
1072 my $running = PVE
::QemuServer
::check_running
($vmid);
1074 # apply pending changes
1076 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1080 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1081 raise_param_exc
($errors) if scalar(keys %$errors);
1083 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1093 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1095 if ($background_delay) {
1097 # Note: It would be better to do that in the Event based HTTPServer
1098 # to avoid blocking call to sleep.
1100 my $end_time = time() + $background_delay;
1102 my $task = PVE
::Tools
::upid_decode
($upid);
1105 while (time() < $end_time) {
1106 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1108 sleep(1); # this gets interrupted when child process ends
1112 my $status = PVE
::Tools
::upid_read_status
($upid);
1113 return undef if $status eq 'OK';
1122 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1125 my $vm_config_perm_list = [
1130 'VM.Config.Network',
1132 'VM.Config.Options',
1135 __PACKAGE__-
>register_method({
1136 name
=> 'update_vm_async',
1137 path
=> '{vmid}/config',
1141 description
=> "Set virtual machine options (asynchrounous API).",
1143 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1146 additionalProperties
=> 0,
1147 properties
=> PVE
::QemuServer
::json_config_properties
(
1149 node
=> get_standard_option
('pve-node'),
1150 vmid
=> get_standard_option
('pve-vmid'),
1151 skiplock
=> get_standard_option
('skiplock'),
1153 type
=> 'string', format
=> 'pve-configid-list',
1154 description
=> "A list of settings you want to delete.",
1158 type
=> 'string', format
=> 'pve-configid-list',
1159 description
=> "Revert a pending change.",
1164 description
=> $opt_force_description,
1166 requires
=> 'delete',
1170 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1174 background_delay
=> {
1176 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1187 code
=> $update_vm_api,
1190 __PACKAGE__-
>register_method({
1191 name
=> 'update_vm',
1192 path
=> '{vmid}/config',
1196 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1198 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1201 additionalProperties
=> 0,
1202 properties
=> PVE
::QemuServer
::json_config_properties
(
1204 node
=> get_standard_option
('pve-node'),
1205 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1206 skiplock
=> get_standard_option
('skiplock'),
1208 type
=> 'string', format
=> 'pve-configid-list',
1209 description
=> "A list of settings you want to delete.",
1213 type
=> 'string', format
=> 'pve-configid-list',
1214 description
=> "Revert a pending change.",
1219 description
=> $opt_force_description,
1221 requires
=> 'delete',
1225 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1231 returns
=> { type
=> 'null' },
1234 &$update_vm_api($param, 1);
1240 __PACKAGE__-
>register_method({
1241 name
=> 'destroy_vm',
1246 description
=> "Destroy the vm (also delete all used/owned volumes).",
1248 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1251 additionalProperties
=> 0,
1253 node
=> get_standard_option
('pve-node'),
1254 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1255 skiplock
=> get_standard_option
('skiplock'),
1264 my $rpcenv = PVE
::RPCEnvironment
::get
();
1266 my $authuser = $rpcenv->get_user();
1268 my $vmid = $param->{vmid
};
1270 my $skiplock = $param->{skiplock
};
1271 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1272 if $skiplock && $authuser ne 'root@pam';
1275 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1277 my $storecfg = PVE
::Storage
::config
();
1279 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1281 die "unable to remove VM $vmid - used in HA resources\n"
1282 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1284 # do not allow destroy if there are replication jobs
1285 my $repl_conf = PVE
::ReplicationConfig-
>new();
1286 $repl_conf->check_for_existing_jobs($vmid);
1288 # early tests (repeat after locking)
1289 die "VM $vmid is running - destroy failed\n"
1290 if PVE
::QemuServer
::check_running
($vmid);
1295 syslog
('info', "destroy VM $vmid: $upid\n");
1297 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1299 PVE
::AccessControl
::remove_vm_access
($vmid);
1301 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1304 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1307 __PACKAGE__-
>register_method({
1309 path
=> '{vmid}/unlink',
1313 description
=> "Unlink/delete disk images.",
1315 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1318 additionalProperties
=> 0,
1320 node
=> get_standard_option
('pve-node'),
1321 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1323 type
=> 'string', format
=> 'pve-configid-list',
1324 description
=> "A list of disk IDs you want to delete.",
1328 description
=> $opt_force_description,
1333 returns
=> { type
=> 'null'},
1337 $param->{delete} = extract_param
($param, 'idlist');
1339 __PACKAGE__-
>update_vm($param);
1346 __PACKAGE__-
>register_method({
1348 path
=> '{vmid}/vncproxy',
1352 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1354 description
=> "Creates a TCP VNC proxy connections.",
1356 additionalProperties
=> 0,
1358 node
=> get_standard_option
('pve-node'),
1359 vmid
=> get_standard_option
('pve-vmid'),
1363 description
=> "starts websockify instead of vncproxy",
1368 additionalProperties
=> 0,
1370 user
=> { type
=> 'string' },
1371 ticket
=> { type
=> 'string' },
1372 cert
=> { type
=> 'string' },
1373 port
=> { type
=> 'integer' },
1374 upid
=> { type
=> 'string' },
1380 my $rpcenv = PVE
::RPCEnvironment
::get
();
1382 my $authuser = $rpcenv->get_user();
1384 my $vmid = $param->{vmid
};
1385 my $node = $param->{node
};
1386 my $websocket = $param->{websocket
};
1388 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1390 my $authpath = "/vms/$vmid";
1392 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1394 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1397 my ($remip, $family);
1400 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1401 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1402 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1403 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1405 $family = PVE
::Tools
::get_host_address_family
($node);
1408 my $port = PVE
::Tools
::next_vnc_port
($family);
1415 syslog
('info', "starting vnc proxy $upid\n");
1419 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1421 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1423 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1424 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1425 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1426 '-timeout', $timeout, '-authpath', $authpath,
1427 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1428 PVE
::Tools
::run_command
($cmd);
1431 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1433 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1435 my $sock = IO
::Socket
::IP-
>new(
1440 GetAddrInfoFlags
=> 0,
1441 ) or die "failed to create socket: $!\n";
1442 # Inside the worker we shouldn't have any previous alarms
1443 # running anyway...:
1445 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1447 accept(my $cli, $sock) or die "connection failed: $!\n";
1450 if (PVE
::Tools
::run_command
($cmd,
1451 output
=> '>&'.fileno($cli),
1452 input
=> '<&'.fileno($cli),
1455 die "Failed to run vncproxy.\n";
1462 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1464 PVE
::Tools
::wait_for_vnc_port
($port);
1475 __PACKAGE__-
>register_method({
1476 name
=> 'vncwebsocket',
1477 path
=> '{vmid}/vncwebsocket',
1480 description
=> "You also need to pass a valid ticket (vncticket).",
1481 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1483 description
=> "Opens a weksocket for VNC traffic.",
1485 additionalProperties
=> 0,
1487 node
=> get_standard_option
('pve-node'),
1488 vmid
=> get_standard_option
('pve-vmid'),
1490 description
=> "Ticket from previous call to vncproxy.",
1495 description
=> "Port number returned by previous vncproxy call.",
1505 port
=> { type
=> 'string' },
1511 my $rpcenv = PVE
::RPCEnvironment
::get
();
1513 my $authuser = $rpcenv->get_user();
1515 my $vmid = $param->{vmid
};
1516 my $node = $param->{node
};
1518 my $authpath = "/vms/$vmid";
1520 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1522 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1524 # Note: VNC ports are acessible from outside, so we do not gain any
1525 # security if we verify that $param->{port} belongs to VM $vmid. This
1526 # check is done by verifying the VNC ticket (inside VNC protocol).
1528 my $port = $param->{port
};
1530 return { port
=> $port };
1533 __PACKAGE__-
>register_method({
1534 name
=> 'spiceproxy',
1535 path
=> '{vmid}/spiceproxy',
1540 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1542 description
=> "Returns a SPICE configuration to connect to the VM.",
1544 additionalProperties
=> 0,
1546 node
=> get_standard_option
('pve-node'),
1547 vmid
=> get_standard_option
('pve-vmid'),
1548 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1551 returns
=> get_standard_option
('remote-viewer-config'),
1555 my $rpcenv = PVE
::RPCEnvironment
::get
();
1557 my $authuser = $rpcenv->get_user();
1559 my $vmid = $param->{vmid
};
1560 my $node = $param->{node
};
1561 my $proxy = $param->{proxy
};
1563 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1564 my $title = "VM $vmid";
1565 $title .= " - ". $conf->{name
} if $conf->{name
};
1567 my $port = PVE
::QemuServer
::spice_port
($vmid);
1569 my ($ticket, undef, $remote_viewer_config) =
1570 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1572 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1573 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1575 return $remote_viewer_config;
1578 __PACKAGE__-
>register_method({
1580 path
=> '{vmid}/status',
1583 description
=> "Directory index",
1588 additionalProperties
=> 0,
1590 node
=> get_standard_option
('pve-node'),
1591 vmid
=> get_standard_option
('pve-vmid'),
1599 subdir
=> { type
=> 'string' },
1602 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1608 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1611 { subdir
=> 'current' },
1612 { subdir
=> 'start' },
1613 { subdir
=> 'stop' },
1619 __PACKAGE__-
>register_method({
1620 name
=> 'vm_status',
1621 path
=> '{vmid}/status/current',
1624 protected
=> 1, # qemu pid files are only readable by root
1625 description
=> "Get virtual machine status.",
1627 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1630 additionalProperties
=> 0,
1632 node
=> get_standard_option
('pve-node'),
1633 vmid
=> get_standard_option
('pve-vmid'),
1636 returns
=> { type
=> 'object' },
1641 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1643 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1644 my $status = $vmstatus->{$param->{vmid
}};
1646 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1648 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1653 __PACKAGE__-
>register_method({
1655 path
=> '{vmid}/status/start',
1659 description
=> "Start virtual machine.",
1661 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1664 additionalProperties
=> 0,
1666 node
=> get_standard_option
('pve-node'),
1667 vmid
=> get_standard_option
('pve-vmid',
1668 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1669 skiplock
=> get_standard_option
('skiplock'),
1670 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1671 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1674 enum
=> ['secure', 'insecure'],
1675 description
=> "Migration traffic is encrypted using an SSH " .
1676 "tunnel by default. On secure, completely private networks " .
1677 "this can be disabled to increase performance.",
1680 migration_network
=> {
1681 type
=> 'string', format
=> 'CIDR',
1682 description
=> "CIDR of the (sub) network that is used for migration.",
1685 machine
=> get_standard_option
('pve-qm-machine'),
1687 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1699 my $rpcenv = PVE
::RPCEnvironment
::get
();
1701 my $authuser = $rpcenv->get_user();
1703 my $node = extract_param
($param, 'node');
1705 my $vmid = extract_param
($param, 'vmid');
1707 my $machine = extract_param
($param, 'machine');
1709 my $stateuri = extract_param
($param, 'stateuri');
1710 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1711 if $stateuri && $authuser ne 'root@pam';
1713 my $skiplock = extract_param
($param, 'skiplock');
1714 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1715 if $skiplock && $authuser ne 'root@pam';
1717 my $migratedfrom = extract_param
($param, 'migratedfrom');
1718 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1719 if $migratedfrom && $authuser ne 'root@pam';
1721 my $migration_type = extract_param
($param, 'migration_type');
1722 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1723 if $migration_type && $authuser ne 'root@pam';
1725 my $migration_network = extract_param
($param, 'migration_network');
1726 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1727 if $migration_network && $authuser ne 'root@pam';
1729 my $targetstorage = extract_param
($param, 'targetstorage');
1730 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1731 if $targetstorage && $authuser ne 'root@pam';
1733 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1734 if $targetstorage && !$migratedfrom;
1736 # read spice ticket from STDIN
1738 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1739 if (defined(my $line = <>)) {
1741 $spice_ticket = $line;
1745 PVE
::Cluster
::check_cfs_quorum
();
1747 my $storecfg = PVE
::Storage
::config
();
1749 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1750 $rpcenv->{type
} ne 'ha') {
1755 my $service = "vm:$vmid";
1757 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1759 print "Executing HA start for VM $vmid\n";
1761 PVE
::Tools
::run_command
($cmd);
1766 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1773 syslog
('info', "start VM $vmid: $upid\n");
1775 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1776 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1781 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1785 __PACKAGE__-
>register_method({
1787 path
=> '{vmid}/status/stop',
1791 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1792 "is akin to pulling the power plug of a running computer and may damage the VM data",
1794 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1797 additionalProperties
=> 0,
1799 node
=> get_standard_option
('pve-node'),
1800 vmid
=> get_standard_option
('pve-vmid',
1801 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1802 skiplock
=> get_standard_option
('skiplock'),
1803 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1805 description
=> "Wait maximal timeout seconds.",
1811 description
=> "Do not deactivate storage volumes.",
1824 my $rpcenv = PVE
::RPCEnvironment
::get
();
1826 my $authuser = $rpcenv->get_user();
1828 my $node = extract_param
($param, 'node');
1830 my $vmid = extract_param
($param, 'vmid');
1832 my $skiplock = extract_param
($param, 'skiplock');
1833 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1834 if $skiplock && $authuser ne 'root@pam';
1836 my $keepActive = extract_param
($param, 'keepActive');
1837 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1838 if $keepActive && $authuser ne 'root@pam';
1840 my $migratedfrom = extract_param
($param, 'migratedfrom');
1841 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1842 if $migratedfrom && $authuser ne 'root@pam';
1845 my $storecfg = PVE
::Storage
::config
();
1847 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1852 my $service = "vm:$vmid";
1854 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1856 print "Executing HA stop for VM $vmid\n";
1858 PVE
::Tools
::run_command
($cmd);
1863 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1869 syslog
('info', "stop VM $vmid: $upid\n");
1871 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1872 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1877 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1881 __PACKAGE__-
>register_method({
1883 path
=> '{vmid}/status/reset',
1887 description
=> "Reset virtual machine.",
1889 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1892 additionalProperties
=> 0,
1894 node
=> get_standard_option
('pve-node'),
1895 vmid
=> get_standard_option
('pve-vmid',
1896 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1897 skiplock
=> get_standard_option
('skiplock'),
1906 my $rpcenv = PVE
::RPCEnvironment
::get
();
1908 my $authuser = $rpcenv->get_user();
1910 my $node = extract_param
($param, 'node');
1912 my $vmid = extract_param
($param, 'vmid');
1914 my $skiplock = extract_param
($param, 'skiplock');
1915 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1916 if $skiplock && $authuser ne 'root@pam';
1918 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1923 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1928 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1931 __PACKAGE__-
>register_method({
1932 name
=> 'vm_shutdown',
1933 path
=> '{vmid}/status/shutdown',
1937 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1938 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1940 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1943 additionalProperties
=> 0,
1945 node
=> get_standard_option
('pve-node'),
1946 vmid
=> get_standard_option
('pve-vmid',
1947 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1948 skiplock
=> get_standard_option
('skiplock'),
1950 description
=> "Wait maximal timeout seconds.",
1956 description
=> "Make sure the VM stops.",
1962 description
=> "Do not deactivate storage volumes.",
1975 my $rpcenv = PVE
::RPCEnvironment
::get
();
1977 my $authuser = $rpcenv->get_user();
1979 my $node = extract_param
($param, 'node');
1981 my $vmid = extract_param
($param, 'vmid');
1983 my $skiplock = extract_param
($param, 'skiplock');
1984 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1985 if $skiplock && $authuser ne 'root@pam';
1987 my $keepActive = extract_param
($param, 'keepActive');
1988 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1989 if $keepActive && $authuser ne 'root@pam';
1991 my $storecfg = PVE
::Storage
::config
();
1995 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1996 # otherwise, we will infer a shutdown command, but run into the timeout,
1997 # then when the vm is resumed, it will instantly shutdown
1999 # checking the qmp status here to get feedback to the gui/cli/api
2000 # and the status query should not take too long
2003 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2007 if (!$err && $qmpstatus->{status
} eq "paused") {
2008 if ($param->{forceStop
}) {
2009 warn "VM is paused - stop instead of shutdown\n";
2012 die "VM is paused - cannot shutdown\n";
2016 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2017 ($rpcenv->{type
} ne 'ha')) {
2022 my $service = "vm:$vmid";
2024 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2026 print "Executing HA stop for VM $vmid\n";
2028 PVE
::Tools
::run_command
($cmd);
2033 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2040 syslog
('info', "shutdown VM $vmid: $upid\n");
2042 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2043 $shutdown, $param->{forceStop
}, $keepActive);
2048 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2052 __PACKAGE__-
>register_method({
2053 name
=> 'vm_suspend',
2054 path
=> '{vmid}/status/suspend',
2058 description
=> "Suspend virtual machine.",
2060 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2063 additionalProperties
=> 0,
2065 node
=> get_standard_option
('pve-node'),
2066 vmid
=> get_standard_option
('pve-vmid',
2067 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2068 skiplock
=> get_standard_option
('skiplock'),
2077 my $rpcenv = PVE
::RPCEnvironment
::get
();
2079 my $authuser = $rpcenv->get_user();
2081 my $node = extract_param
($param, 'node');
2083 my $vmid = extract_param
($param, 'vmid');
2085 my $skiplock = extract_param
($param, 'skiplock');
2086 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2087 if $skiplock && $authuser ne 'root@pam';
2089 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2094 syslog
('info', "suspend VM $vmid: $upid\n");
2096 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2101 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2104 __PACKAGE__-
>register_method({
2105 name
=> 'vm_resume',
2106 path
=> '{vmid}/status/resume',
2110 description
=> "Resume virtual machine.",
2112 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2115 additionalProperties
=> 0,
2117 node
=> get_standard_option
('pve-node'),
2118 vmid
=> get_standard_option
('pve-vmid',
2119 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2120 skiplock
=> get_standard_option
('skiplock'),
2121 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2131 my $rpcenv = PVE
::RPCEnvironment
::get
();
2133 my $authuser = $rpcenv->get_user();
2135 my $node = extract_param
($param, 'node');
2137 my $vmid = extract_param
($param, 'vmid');
2139 my $skiplock = extract_param
($param, 'skiplock');
2140 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2141 if $skiplock && $authuser ne 'root@pam';
2143 my $nocheck = extract_param
($param, 'nocheck');
2145 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2150 syslog
('info', "resume VM $vmid: $upid\n");
2152 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2157 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2160 __PACKAGE__-
>register_method({
2161 name
=> 'vm_sendkey',
2162 path
=> '{vmid}/sendkey',
2166 description
=> "Send key event to virtual machine.",
2168 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2171 additionalProperties
=> 0,
2173 node
=> get_standard_option
('pve-node'),
2174 vmid
=> get_standard_option
('pve-vmid',
2175 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2176 skiplock
=> get_standard_option
('skiplock'),
2178 description
=> "The key (qemu monitor encoding).",
2183 returns
=> { type
=> 'null'},
2187 my $rpcenv = PVE
::RPCEnvironment
::get
();
2189 my $authuser = $rpcenv->get_user();
2191 my $node = extract_param
($param, 'node');
2193 my $vmid = extract_param
($param, 'vmid');
2195 my $skiplock = extract_param
($param, 'skiplock');
2196 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2197 if $skiplock && $authuser ne 'root@pam';
2199 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2204 __PACKAGE__-
>register_method({
2205 name
=> 'vm_feature',
2206 path
=> '{vmid}/feature',
2210 description
=> "Check if feature for virtual machine is available.",
2212 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2215 additionalProperties
=> 0,
2217 node
=> get_standard_option
('pve-node'),
2218 vmid
=> get_standard_option
('pve-vmid'),
2220 description
=> "Feature to check.",
2222 enum
=> [ 'snapshot', 'clone', 'copy' ],
2224 snapname
=> get_standard_option
('pve-snapshot-name', {
2232 hasFeature
=> { type
=> 'boolean' },
2235 items
=> { type
=> 'string' },
2242 my $node = extract_param
($param, 'node');
2244 my $vmid = extract_param
($param, 'vmid');
2246 my $snapname = extract_param
($param, 'snapname');
2248 my $feature = extract_param
($param, 'feature');
2250 my $running = PVE
::QemuServer
::check_running
($vmid);
2252 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2255 my $snap = $conf->{snapshots
}->{$snapname};
2256 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2259 my $storecfg = PVE
::Storage
::config
();
2261 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2262 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2265 hasFeature
=> $hasFeature,
2266 nodes
=> [ keys %$nodelist ],
2270 __PACKAGE__-
>register_method({
2272 path
=> '{vmid}/clone',
2276 description
=> "Create a copy of virtual machine/template.",
2278 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2279 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2280 "'Datastore.AllocateSpace' on any used storage.",
2283 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2285 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2286 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2291 additionalProperties
=> 0,
2293 node
=> get_standard_option
('pve-node'),
2294 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2295 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2298 type
=> 'string', format
=> 'dns-name',
2299 description
=> "Set a name for the new VM.",
2304 description
=> "Description for the new VM.",
2308 type
=> 'string', format
=> 'pve-poolid',
2309 description
=> "Add the new VM to the specified pool.",
2311 snapname
=> get_standard_option
('pve-snapshot-name', {
2314 storage
=> get_standard_option
('pve-storage-id', {
2315 description
=> "Target storage for full clone.",
2320 description
=> "Target format for file storage.",
2324 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2329 description
=> "Create a full copy of all disk. This is always done when " .
2330 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2333 target
=> get_standard_option
('pve-node', {
2334 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2345 my $rpcenv = PVE
::RPCEnvironment
::get
();
2347 my $authuser = $rpcenv->get_user();
2349 my $node = extract_param
($param, 'node');
2351 my $vmid = extract_param
($param, 'vmid');
2353 my $newid = extract_param
($param, 'newid');
2355 my $pool = extract_param
($param, 'pool');
2357 if (defined($pool)) {
2358 $rpcenv->check_pool_exist($pool);
2361 my $snapname = extract_param
($param, 'snapname');
2363 my $storage = extract_param
($param, 'storage');
2365 my $format = extract_param
($param, 'format');
2367 my $target = extract_param
($param, 'target');
2369 my $localnode = PVE
::INotify
::nodename
();
2371 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2373 PVE
::Cluster
::check_node_exists
($target) if $target;
2375 my $storecfg = PVE
::Storage
::config
();
2378 # check if storage is enabled on local node
2379 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2381 # check if storage is available on target node
2382 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2383 # clone only works if target storage is shared
2384 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2385 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2389 PVE
::Cluster
::check_cfs_quorum
();
2391 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2393 # exclusive lock if VM is running - else shared lock is enough;
2394 my $shared_lock = $running ?
0 : 1;
2398 # do all tests after lock
2399 # we also try to do all tests before we fork the worker
2401 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2403 PVE
::QemuConfig-
>check_lock($conf);
2405 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2407 die "unexpected state change\n" if $verify_running != $running;
2409 die "snapshot '$snapname' does not exist\n"
2410 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2412 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2414 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2416 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2418 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2420 die "unable to create VM $newid: config file already exists\n"
2423 my $newconf = { lock => 'clone' };
2428 foreach my $opt (keys %$oldconf) {
2429 my $value = $oldconf->{$opt};
2431 # do not copy snapshot related info
2432 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2433 $opt eq 'vmstate' || $opt eq 'snapstate';
2435 # no need to copy unused images, because VMID(owner) changes anyways
2436 next if $opt =~ m/^unused\d+$/;
2438 # always change MAC! address
2439 if ($opt =~ m/^net(\d+)$/) {
2440 my $net = PVE
::QemuServer
::parse_net
($value);
2441 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2442 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2443 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2444 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2445 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2446 die "unable to parse drive options for '$opt'\n" if !$drive;
2447 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2448 $newconf->{$opt} = $value; # simply copy configuration
2450 if ($param->{full
}) {
2451 die "Full clone feature is not supported for drive '$opt'\n"
2452 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2453 $fullclone->{$opt} = 1;
2455 # not full means clone instead of copy
2456 die "Linked clone feature is not supported for drive '$opt'\n"
2457 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2459 $drives->{$opt} = $drive;
2460 push @$vollist, $drive->{file
};
2463 # copy everything else
2464 $newconf->{$opt} = $value;
2468 # auto generate a new uuid
2469 my ($uuid, $uuid_str);
2470 UUID
::generate
($uuid);
2471 UUID
::unparse
($uuid, $uuid_str);
2472 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2473 $smbios1->{uuid
} = $uuid_str;
2474 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2476 delete $newconf->{template
};
2478 if ($param->{name
}) {
2479 $newconf->{name
} = $param->{name
};
2481 if ($oldconf->{name
}) {
2482 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2484 $newconf->{name
} = "Copy-of-VM-$vmid";
2488 if ($param->{description
}) {
2489 $newconf->{description
} = $param->{description
};
2492 # create empty/temp config - this fails if VM already exists on other node
2493 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2498 my $newvollist = [];
2502 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2504 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2506 my $total_jobs = scalar(keys %{$drives});
2509 foreach my $opt (keys %$drives) {
2510 my $drive = $drives->{$opt};
2511 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2513 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2514 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2515 $jobs, $skipcomplete, $oldconf->{agent
});
2517 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2519 PVE
::QemuConfig-
>write_config($newid, $newconf);
2523 delete $newconf->{lock};
2524 PVE
::QemuConfig-
>write_config($newid, $newconf);
2527 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2528 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2529 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2531 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2532 die "Failed to move config to node '$target' - rename failed: $!\n"
2533 if !rename($conffile, $newconffile);
2536 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2541 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2543 sleep 1; # some storage like rbd need to wait before release volume - really?
2545 foreach my $volid (@$newvollist) {
2546 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2549 die "clone failed: $err";
2555 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2557 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2560 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2561 # Aquire exclusive lock lock for $newid
2562 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2567 __PACKAGE__-
>register_method({
2568 name
=> 'move_vm_disk',
2569 path
=> '{vmid}/move_disk',
2573 description
=> "Move volume to different storage.",
2575 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2577 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2578 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2582 additionalProperties
=> 0,
2584 node
=> get_standard_option
('pve-node'),
2585 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2588 description
=> "The disk you want to move.",
2589 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2591 storage
=> get_standard_option
('pve-storage-id', {
2592 description
=> "Target storage.",
2593 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2597 description
=> "Target Format.",
2598 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2603 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2609 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2617 description
=> "the task ID.",
2622 my $rpcenv = PVE
::RPCEnvironment
::get
();
2624 my $authuser = $rpcenv->get_user();
2626 my $node = extract_param
($param, 'node');
2628 my $vmid = extract_param
($param, 'vmid');
2630 my $digest = extract_param
($param, 'digest');
2632 my $disk = extract_param
($param, 'disk');
2634 my $storeid = extract_param
($param, 'storage');
2636 my $format = extract_param
($param, 'format');
2638 my $storecfg = PVE
::Storage
::config
();
2640 my $updatefn = sub {
2642 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2644 PVE
::QemuConfig-
>check_lock($conf);
2646 die "checksum missmatch (file change by other user?)\n"
2647 if $digest && $digest ne $conf->{digest
};
2649 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2651 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2653 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2655 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2658 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2659 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2663 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2664 (!$format || !$oldfmt || $oldfmt eq $format);
2666 # this only checks snapshots because $disk is passed!
2667 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2668 die "you can't move a disk with snapshots and delete the source\n"
2669 if $snapshotted && $param->{delete};
2671 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2673 my $running = PVE
::QemuServer
::check_running
($vmid);
2675 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2679 my $newvollist = [];
2682 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2684 warn "moving disk with snapshots, snapshots will not be moved!\n"
2687 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2688 $vmid, $storeid, $format, 1, $newvollist);
2690 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2692 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2694 # convert moved disk to base if part of template
2695 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2696 if PVE
::QemuConfig-
>is_template($conf);
2698 PVE
::QemuConfig-
>write_config($vmid, $conf);
2701 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2702 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2709 foreach my $volid (@$newvollist) {
2710 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2713 die "storage migration failed: $err";
2716 if ($param->{delete}) {
2718 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2719 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2725 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2728 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2731 __PACKAGE__-
>register_method({
2732 name
=> 'migrate_vm',
2733 path
=> '{vmid}/migrate',
2737 description
=> "Migrate virtual machine. Creates a new migration task.",
2739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2742 additionalProperties
=> 0,
2744 node
=> get_standard_option
('pve-node'),
2745 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2746 target
=> get_standard_option
('pve-node', {
2747 description
=> "Target node.",
2748 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2752 description
=> "Use online/live migration.",
2757 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2762 enum
=> ['secure', 'insecure'],
2763 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2766 migration_network
=> {
2767 type
=> 'string', format
=> 'CIDR',
2768 description
=> "CIDR of the (sub) network that is used for migration.",
2771 "with-local-disks" => {
2773 description
=> "Enable live storage migration for local disk",
2776 targetstorage
=> get_standard_option
('pve-storage-id', {
2777 description
=> "Default target storage.",
2779 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2785 description
=> "the task ID.",
2790 my $rpcenv = PVE
::RPCEnvironment
::get
();
2792 my $authuser = $rpcenv->get_user();
2794 my $target = extract_param
($param, 'target');
2796 my $localnode = PVE
::INotify
::nodename
();
2797 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2799 PVE
::Cluster
::check_cfs_quorum
();
2801 PVE
::Cluster
::check_node_exists
($target);
2803 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2805 my $vmid = extract_param
($param, 'vmid');
2807 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2808 if !$param->{online
} && $param->{targetstorage
};
2810 raise_param_exc
({ force
=> "Only root may use this option." })
2811 if $param->{force
} && $authuser ne 'root@pam';
2813 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2814 if $param->{migration_type
} && $authuser ne 'root@pam';
2816 # allow root only until better network permissions are available
2817 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2818 if $param->{migration_network
} && $authuser ne 'root@pam';
2821 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2823 # try to detect errors early
2825 PVE
::QemuConfig-
>check_lock($conf);
2827 if (PVE
::QemuServer
::check_running
($vmid)) {
2828 die "cant migrate running VM without --online\n"
2829 if !$param->{online
};
2832 my $storecfg = PVE
::Storage
::config
();
2834 if( $param->{targetstorage
}) {
2835 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2837 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2840 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2845 my $service = "vm:$vmid";
2847 my $cmd = ['ha-manager', 'migrate', $service, $target];
2849 print "Executing HA migrate for VM $vmid to node $target\n";
2851 PVE
::Tools
::run_command
($cmd);
2856 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2864 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2867 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2870 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $code);
2875 __PACKAGE__-
>register_method({
2877 path
=> '{vmid}/monitor',
2881 description
=> "Execute Qemu monitor commands.",
2883 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2884 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2887 additionalProperties
=> 0,
2889 node
=> get_standard_option
('pve-node'),
2890 vmid
=> get_standard_option
('pve-vmid'),
2893 description
=> "The monitor command.",
2897 returns
=> { type
=> 'string'},
2901 my $rpcenv = PVE
::RPCEnvironment
::get
();
2902 my $authuser = $rpcenv->get_user();
2905 my $command = shift;
2906 return $command =~ m/^\s*info(\s+|$)/
2907 || $command =~ m/^\s*help\s*$/;
2910 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2911 if !&$is_ro($param->{command
});
2913 my $vmid = $param->{vmid
};
2915 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2919 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2921 $res = "ERROR: $@" if $@;
2926 my $guest_agent_commands = [
2934 'network-get-interfaces',
2937 'get-memory-blocks',
2938 'get-memory-block-info',
2945 __PACKAGE__-
>register_method({
2947 path
=> '{vmid}/agent',
2951 description
=> "Execute Qemu Guest Agent commands.",
2953 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2956 additionalProperties
=> 0,
2958 node
=> get_standard_option
('pve-node'),
2959 vmid
=> get_standard_option
('pve-vmid', {
2960 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2963 description
=> "The QGA command.",
2964 enum
=> $guest_agent_commands,
2970 description
=> "Returns an object with a single `result` property. The type of that
2971 property depends on the executed command.",
2976 my $vmid = $param->{vmid
};
2978 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2980 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2981 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2983 my $cmd = $param->{command
};
2985 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2987 return { result
=> $res };
2990 __PACKAGE__-
>register_method({
2991 name
=> 'resize_vm',
2992 path
=> '{vmid}/resize',
2996 description
=> "Extend volume size.",
2998 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3001 additionalProperties
=> 0,
3003 node
=> get_standard_option
('pve-node'),
3004 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3005 skiplock
=> get_standard_option
('skiplock'),
3008 description
=> "The disk you want to resize.",
3009 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3013 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3014 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.",
3018 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3024 returns
=> { type
=> 'null'},
3028 my $rpcenv = PVE
::RPCEnvironment
::get
();
3030 my $authuser = $rpcenv->get_user();
3032 my $node = extract_param
($param, 'node');
3034 my $vmid = extract_param
($param, 'vmid');
3036 my $digest = extract_param
($param, 'digest');
3038 my $disk = extract_param
($param, 'disk');
3040 my $sizestr = extract_param
($param, 'size');
3042 my $skiplock = extract_param
($param, 'skiplock');
3043 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3044 if $skiplock && $authuser ne 'root@pam';
3046 my $storecfg = PVE
::Storage
::config
();
3048 my $updatefn = sub {
3050 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3052 die "checksum missmatch (file change by other user?)\n"
3053 if $digest && $digest ne $conf->{digest
};
3054 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3056 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3058 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3060 my (undef, undef, undef, undef, undef, undef, $format) =
3061 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3063 die "can't resize volume: $disk if snapshot exists\n"
3064 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3066 my $volid = $drive->{file
};
3068 die "disk '$disk' has no associated volume\n" if !$volid;
3070 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3072 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3074 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3076 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3077 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3079 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3080 my ($ext, $newsize, $unit) = ($1, $2, $4);
3083 $newsize = $newsize * 1024;
3084 } elsif ($unit eq 'M') {
3085 $newsize = $newsize * 1024 * 1024;
3086 } elsif ($unit eq 'G') {
3087 $newsize = $newsize * 1024 * 1024 * 1024;
3088 } elsif ($unit eq 'T') {
3089 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3092 $newsize += $size if $ext;
3093 $newsize = int($newsize);
3095 die "shrinking disks is not supported\n" if $newsize < $size;
3097 return if $size == $newsize;
3099 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3101 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3103 $drive->{size
} = $newsize;
3104 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3106 PVE
::QemuConfig-
>write_config($vmid, $conf);
3109 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3113 __PACKAGE__-
>register_method({
3114 name
=> 'snapshot_list',
3115 path
=> '{vmid}/snapshot',
3117 description
=> "List all snapshots.",
3119 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3122 protected
=> 1, # qemu pid files are only readable by root
3124 additionalProperties
=> 0,
3126 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3127 node
=> get_standard_option
('pve-node'),
3136 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3141 my $vmid = $param->{vmid
};
3143 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3144 my $snaphash = $conf->{snapshots
} || {};
3148 foreach my $name (keys %$snaphash) {
3149 my $d = $snaphash->{$name};
3152 snaptime
=> $d->{snaptime
} || 0,
3153 vmstate
=> $d->{vmstate
} ?
1 : 0,
3154 description
=> $d->{description
} || '',
3156 $item->{parent
} = $d->{parent
} if $d->{parent
};
3157 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3161 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3162 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3163 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3165 push @$res, $current;
3170 __PACKAGE__-
>register_method({
3172 path
=> '{vmid}/snapshot',
3176 description
=> "Snapshot a VM.",
3178 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3181 additionalProperties
=> 0,
3183 node
=> get_standard_option
('pve-node'),
3184 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3185 snapname
=> get_standard_option
('pve-snapshot-name'),
3189 description
=> "Save the vmstate",
3194 description
=> "A textual description or comment.",
3200 description
=> "the task ID.",
3205 my $rpcenv = PVE
::RPCEnvironment
::get
();
3207 my $authuser = $rpcenv->get_user();
3209 my $node = extract_param
($param, 'node');
3211 my $vmid = extract_param
($param, 'vmid');
3213 my $snapname = extract_param
($param, 'snapname');
3215 die "unable to use snapshot name 'current' (reserved name)\n"
3216 if $snapname eq 'current';
3219 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3220 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3221 $param->{description
});
3224 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3227 __PACKAGE__-
>register_method({
3228 name
=> 'snapshot_cmd_idx',
3229 path
=> '{vmid}/snapshot/{snapname}',
3236 additionalProperties
=> 0,
3238 vmid
=> get_standard_option
('pve-vmid'),
3239 node
=> get_standard_option
('pve-node'),
3240 snapname
=> get_standard_option
('pve-snapshot-name'),
3249 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3256 push @$res, { cmd
=> 'rollback' };
3257 push @$res, { cmd
=> 'config' };
3262 __PACKAGE__-
>register_method({
3263 name
=> 'update_snapshot_config',
3264 path
=> '{vmid}/snapshot/{snapname}/config',
3268 description
=> "Update snapshot metadata.",
3270 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3273 additionalProperties
=> 0,
3275 node
=> get_standard_option
('pve-node'),
3276 vmid
=> get_standard_option
('pve-vmid'),
3277 snapname
=> get_standard_option
('pve-snapshot-name'),
3281 description
=> "A textual description or comment.",
3285 returns
=> { type
=> 'null' },
3289 my $rpcenv = PVE
::RPCEnvironment
::get
();
3291 my $authuser = $rpcenv->get_user();
3293 my $vmid = extract_param
($param, 'vmid');
3295 my $snapname = extract_param
($param, 'snapname');
3297 return undef if !defined($param->{description
});
3299 my $updatefn = sub {
3301 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3303 PVE
::QemuConfig-
>check_lock($conf);
3305 my $snap = $conf->{snapshots
}->{$snapname};
3307 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3309 $snap->{description
} = $param->{description
} if defined($param->{description
});
3311 PVE
::QemuConfig-
>write_config($vmid, $conf);
3314 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3319 __PACKAGE__-
>register_method({
3320 name
=> 'get_snapshot_config',
3321 path
=> '{vmid}/snapshot/{snapname}/config',
3324 description
=> "Get snapshot configuration",
3326 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3329 additionalProperties
=> 0,
3331 node
=> get_standard_option
('pve-node'),
3332 vmid
=> get_standard_option
('pve-vmid'),
3333 snapname
=> get_standard_option
('pve-snapshot-name'),
3336 returns
=> { type
=> "object" },
3340 my $rpcenv = PVE
::RPCEnvironment
::get
();
3342 my $authuser = $rpcenv->get_user();
3344 my $vmid = extract_param
($param, 'vmid');
3346 my $snapname = extract_param
($param, 'snapname');
3348 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3350 my $snap = $conf->{snapshots
}->{$snapname};
3352 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3357 __PACKAGE__-
>register_method({
3359 path
=> '{vmid}/snapshot/{snapname}/rollback',
3363 description
=> "Rollback VM state to specified snapshot.",
3365 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3368 additionalProperties
=> 0,
3370 node
=> get_standard_option
('pve-node'),
3371 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3372 snapname
=> get_standard_option
('pve-snapshot-name'),
3377 description
=> "the task ID.",
3382 my $rpcenv = PVE
::RPCEnvironment
::get
();
3384 my $authuser = $rpcenv->get_user();
3386 my $node = extract_param
($param, 'node');
3388 my $vmid = extract_param
($param, 'vmid');
3390 my $snapname = extract_param
($param, 'snapname');
3393 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3394 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3397 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3400 __PACKAGE__-
>register_method({
3401 name
=> 'delsnapshot',
3402 path
=> '{vmid}/snapshot/{snapname}',
3406 description
=> "Delete a VM snapshot.",
3408 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3411 additionalProperties
=> 0,
3413 node
=> get_standard_option
('pve-node'),
3414 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3415 snapname
=> get_standard_option
('pve-snapshot-name'),
3419 description
=> "For removal from config file, even if removing disk snapshots fails.",
3425 description
=> "the task ID.",
3430 my $rpcenv = PVE
::RPCEnvironment
::get
();
3432 my $authuser = $rpcenv->get_user();
3434 my $node = extract_param
($param, 'node');
3436 my $vmid = extract_param
($param, 'vmid');
3438 my $snapname = extract_param
($param, 'snapname');
3441 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3442 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3445 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3448 __PACKAGE__-
>register_method({
3450 path
=> '{vmid}/template',
3454 description
=> "Create a Template.",
3456 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3457 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3460 additionalProperties
=> 0,
3462 node
=> get_standard_option
('pve-node'),
3463 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3467 description
=> "If you want to convert only 1 disk to base image.",
3468 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3473 returns
=> { type
=> 'null'},
3477 my $rpcenv = PVE
::RPCEnvironment
::get
();
3479 my $authuser = $rpcenv->get_user();
3481 my $node = extract_param
($param, 'node');
3483 my $vmid = extract_param
($param, 'vmid');
3485 my $disk = extract_param
($param, 'disk');
3487 my $updatefn = sub {
3489 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3491 PVE
::QemuConfig-
>check_lock($conf);
3493 die "unable to create template, because VM contains snapshots\n"
3494 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3496 die "you can't convert a template to a template\n"
3497 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3499 die "you can't convert a VM to template if VM is running\n"
3500 if PVE
::QemuServer
::check_running
($vmid);
3503 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3506 $conf->{template
} = 1;
3507 PVE
::QemuConfig-
>write_config($vmid, $conf);
3509 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3512 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);