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
;
29 if (!$ENV{PVE_GENERATING_DOCS
}) {
30 require PVE
::HA
::Env
::PVE2
;
31 import PVE
::HA
::Env
::PVE2
;
32 require PVE
::HA
::Config
;
33 import PVE
::HA
::Config
;
37 use Data
::Dumper
; # fixme: remove
39 use base
qw(PVE::RESTHandler);
41 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.";
43 my $resolve_cdrom_alias = sub {
46 if (my $value = $param->{cdrom
}) {
47 $value .= ",media=cdrom" if $value !~ m/media=/;
48 $param->{ide2
} = $value;
49 delete $param->{cdrom
};
53 my $check_storage_access = sub {
54 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
56 PVE
::QemuServer
::foreach_drive
($settings, sub {
57 my ($ds, $drive) = @_;
59 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
61 my $volid = $drive->{file
};
63 if (!$volid || $volid eq 'none') {
65 } elsif ($isCDROM && ($volid eq 'cdrom')) {
66 $rpcenv->check($authuser, "/", ['Sys.Console']);
67 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
68 my ($storeid, $size) = ($2 || $default_storage, $3);
69 die "no storage ID specified (and no default storage)\n" if !$storeid;
70 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
72 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
77 my $check_storage_access_clone = sub {
78 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
82 PVE
::QemuServer
::foreach_drive
($conf, sub {
83 my ($ds, $drive) = @_;
85 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
87 my $volid = $drive->{file
};
89 return if !$volid || $volid eq 'none';
92 if ($volid eq 'cdrom') {
93 $rpcenv->check($authuser, "/", ['Sys.Console']);
95 # we simply allow access
96 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
97 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
98 $sharedvm = 0 if !$scfg->{shared
};
102 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
103 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
104 $sharedvm = 0 if !$scfg->{shared
};
106 $sid = $storage if $storage;
107 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
114 # Note: $pool is only needed when creating a VM, because pool permissions
115 # are automatically inherited if VM already exists inside a pool.
116 my $create_disks = sub {
117 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
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 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
198 # free allocated images on error
200 syslog
('err', "VM $vmid creating disks failed");
201 foreach my $volid (@$vollist) {
202 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
208 # modify vm config if everything went well
209 foreach my $ds (keys %$res) {
210 $conf->{$ds} = $res->{$ds};
227 my $memoryoptions = {
233 my $hwtypeoptions = {
245 my $generaloptions = {
252 'migrate_downtime' => 1,
253 'migrate_speed' => 1,
265 my $vmpoweroptions = {
274 my $check_vm_modify_config_perm = sub {
275 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
277 return 1 if $authuser eq 'root@pam';
279 foreach my $opt (@$key_list) {
280 # disk checks need to be done somewhere else
281 next if PVE
::QemuServer
::is_valid_drivename
($opt);
282 next if $opt eq 'cdrom';
283 next if $opt =~ m/^unused\d+$/;
285 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
287 } elsif ($memoryoptions->{$opt}) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
289 } elsif ($hwtypeoptions->{$opt}) {
290 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
291 } elsif ($generaloptions->{$opt}) {
292 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
293 # special case for startup since it changes host behaviour
294 if ($opt eq 'startup') {
295 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
297 } elsif ($vmpoweroptions->{$opt}) {
298 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
299 } elsif ($diskoptions->{$opt}) {
300 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
301 } elsif ($opt =~ m/^net\d+$/) {
302 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
304 # catches usb\d+, hostpci\d+, args, lock, etc.
305 # new options will be checked here
306 die "only root can set '$opt' config\n";
313 __PACKAGE__-
>register_method({
317 description
=> "Virtual machine index (per node).",
319 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
323 protected
=> 1, # qemu pid files are only readable by root
325 additionalProperties
=> 0,
327 node
=> get_standard_option
('pve-node'),
331 description
=> "Determine the full status of active VMs.",
341 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
346 my $rpcenv = PVE
::RPCEnvironment
::get
();
347 my $authuser = $rpcenv->get_user();
349 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
352 foreach my $vmid (keys %$vmstatus) {
353 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
355 my $data = $vmstatus->{$vmid};
356 $data->{vmid
} = int($vmid);
365 __PACKAGE__-
>register_method({
369 description
=> "Create or restore a virtual machine.",
371 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
372 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
373 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
374 user
=> 'all', # check inside
379 additionalProperties
=> 0,
380 properties
=> PVE
::QemuServer
::json_config_properties
(
382 node
=> get_standard_option
('pve-node'),
383 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
385 description
=> "The backup file.",
389 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
391 storage
=> get_standard_option
('pve-storage-id', {
392 description
=> "Default storage.",
394 completion
=> \
&PVE
::QemuServer
::complete_storage
,
399 description
=> "Allow to overwrite existing VM.",
400 requires
=> 'archive',
405 description
=> "Assign a unique random ethernet address.",
406 requires
=> 'archive',
410 type
=> 'string', format
=> 'pve-poolid',
411 description
=> "Add the VM to the specified pool.",
421 my $rpcenv = PVE
::RPCEnvironment
::get
();
423 my $authuser = $rpcenv->get_user();
425 my $node = extract_param
($param, 'node');
427 my $vmid = extract_param
($param, 'vmid');
429 my $archive = extract_param
($param, 'archive');
431 my $storage = extract_param
($param, 'storage');
433 my $force = extract_param
($param, 'force');
435 my $unique = extract_param
($param, 'unique');
437 my $pool = extract_param
($param, 'pool');
439 my $filename = PVE
::QemuConfig-
>config_file($vmid);
441 my $storecfg = PVE
::Storage
::config
();
443 PVE
::Cluster
::check_cfs_quorum
();
445 if (defined($pool)) {
446 $rpcenv->check_pool_exist($pool);
449 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
450 if defined($storage);
452 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
454 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
456 } elsif ($archive && $force && (-f
$filename) &&
457 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
458 # OK: user has VM.Backup permissions, and want to restore an existing VM
464 &$resolve_cdrom_alias($param);
466 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
468 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
470 foreach my $opt (keys %$param) {
471 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
472 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
473 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
475 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
476 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
480 PVE
::QemuServer
::add_random_macs
($param);
482 my $keystr = join(' ', keys %$param);
483 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
485 if ($archive eq '-') {
486 die "pipe requires cli environment\n"
487 if $rpcenv->{type
} ne 'cli';
489 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
490 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
494 my $restorefn = sub {
495 my $vmlist = PVE
::Cluster
::get_vmlist
();
496 if ($vmlist->{ids
}->{$vmid}) {
497 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
498 if ($current_node eq $node) {
499 my $conf = PVE
::QemuConfig-
>load_config($vmid);
501 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
503 die "unable to restore vm $vmid - config file already exists\n"
506 die "unable to restore vm $vmid - vm is running\n"
507 if PVE
::QemuServer
::check_running
($vmid);
509 die "unable to restore vm $vmid - vm is a template\n"
510 if PVE
::QemuConfig-
>is_template($conf);
513 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
518 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
521 unique
=> $unique });
523 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
526 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
532 PVE
::Cluster
::check_vmid_unused
($vmid);
542 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
544 # try to be smart about bootdisk
545 my @disks = PVE
::QemuServer
::valid_drive_names
();
547 foreach my $ds (reverse @disks) {
548 next if !$conf->{$ds};
549 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
550 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
554 if (!$conf->{bootdisk
} && $firstdisk) {
555 $conf->{bootdisk
} = $firstdisk;
558 # auto generate uuid if user did not specify smbios1 option
559 if (!$conf->{smbios1
}) {
560 my ($uuid, $uuid_str);
561 UUID
::generate
($uuid);
562 UUID
::unparse
($uuid, $uuid_str);
563 $conf->{smbios1
} = "uuid=$uuid_str";
566 PVE
::QemuConfig-
>write_config($vmid, $conf);
572 foreach my $volid (@$vollist) {
573 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
576 die "create failed - $err";
579 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
582 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
585 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
588 __PACKAGE__-
>register_method({
593 description
=> "Directory index",
598 additionalProperties
=> 0,
600 node
=> get_standard_option
('pve-node'),
601 vmid
=> get_standard_option
('pve-vmid'),
609 subdir
=> { type
=> 'string' },
612 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
618 { subdir
=> 'config' },
619 { subdir
=> 'pending' },
620 { subdir
=> 'status' },
621 { subdir
=> 'unlink' },
622 { subdir
=> 'vncproxy' },
623 { subdir
=> 'migrate' },
624 { subdir
=> 'resize' },
625 { subdir
=> 'move' },
627 { subdir
=> 'rrddata' },
628 { subdir
=> 'monitor' },
629 { subdir
=> 'agent' },
630 { subdir
=> 'snapshot' },
631 { subdir
=> 'spiceproxy' },
632 { subdir
=> 'sendkey' },
633 { subdir
=> 'firewall' },
639 __PACKAGE__-
>register_method ({
640 subclass
=> "PVE::API2::Firewall::VM",
641 path
=> '{vmid}/firewall',
644 __PACKAGE__-
>register_method({
646 path
=> '{vmid}/rrd',
648 protected
=> 1, # fixme: can we avoid that?
650 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
652 description
=> "Read VM RRD statistics (returns PNG)",
654 additionalProperties
=> 0,
656 node
=> get_standard_option
('pve-node'),
657 vmid
=> get_standard_option
('pve-vmid'),
659 description
=> "Specify the time frame you are interested in.",
661 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
664 description
=> "The list of datasources you want to display.",
665 type
=> 'string', format
=> 'pve-configid-list',
668 description
=> "The RRD consolidation function",
670 enum
=> [ 'AVERAGE', 'MAX' ],
678 filename
=> { type
=> 'string' },
684 return PVE
::Cluster
::create_rrd_graph
(
685 "pve2-vm/$param->{vmid}", $param->{timeframe
},
686 $param->{ds
}, $param->{cf
});
690 __PACKAGE__-
>register_method({
692 path
=> '{vmid}/rrddata',
694 protected
=> 1, # fixme: can we avoid that?
696 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
698 description
=> "Read VM RRD statistics",
700 additionalProperties
=> 0,
702 node
=> get_standard_option
('pve-node'),
703 vmid
=> get_standard_option
('pve-vmid'),
705 description
=> "Specify the time frame you are interested in.",
707 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
710 description
=> "The RRD consolidation function",
712 enum
=> [ 'AVERAGE', 'MAX' ],
727 return PVE
::Cluster
::create_rrd_data
(
728 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
732 __PACKAGE__-
>register_method({
734 path
=> '{vmid}/config',
737 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
742 additionalProperties
=> 0,
744 node
=> get_standard_option
('pve-node'),
745 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
747 description
=> "Get current values (instead of pending values).",
759 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
766 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
768 delete $conf->{snapshots
};
770 if (!$param->{current
}) {
771 foreach my $opt (keys %{$conf->{pending
}}) {
772 next if $opt eq 'delete';
773 my $value = $conf->{pending
}->{$opt};
774 next if ref($value); # just to be sure
775 $conf->{$opt} = $value;
777 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
778 foreach my $opt (keys %$pending_delete_hash) {
779 delete $conf->{$opt} if $conf->{$opt};
783 delete $conf->{pending
};
788 __PACKAGE__-
>register_method({
789 name
=> 'vm_pending',
790 path
=> '{vmid}/pending',
793 description
=> "Get virtual machine configuration, including pending changes.",
795 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
798 additionalProperties
=> 0,
800 node
=> get_standard_option
('pve-node'),
801 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
810 description
=> "Configuration option name.",
814 description
=> "Current value.",
819 description
=> "Pending value.",
824 description
=> "Indicates a pending delete request if present and not 0. " .
825 "The value 2 indicates a force-delete request.",
837 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
839 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
843 foreach my $opt (keys %$conf) {
844 next if ref($conf->{$opt});
845 my $item = { key
=> $opt };
846 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
847 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
848 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
852 foreach my $opt (keys %{$conf->{pending
}}) {
853 next if $opt eq 'delete';
854 next if ref($conf->{pending
}->{$opt}); # just to be sure
855 next if defined($conf->{$opt});
856 my $item = { key
=> $opt };
857 $item->{pending
} = $conf->{pending
}->{$opt};
861 while (my ($opt, $force) = each %$pending_delete_hash) {
862 next if $conf->{pending
}->{$opt}; # just to be sure
863 next if $conf->{$opt};
864 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
871 # POST/PUT {vmid}/config implementation
873 # The original API used PUT (idempotent) an we assumed that all operations
874 # are fast. But it turned out that almost any configuration change can
875 # involve hot-plug actions, or disk alloc/free. Such actions can take long
876 # time to complete and have side effects (not idempotent).
878 # The new implementation uses POST and forks a worker process. We added
879 # a new option 'background_delay'. If specified we wait up to
880 # 'background_delay' second for the worker task to complete. It returns null
881 # if the task is finished within that time, else we return the UPID.
883 my $update_vm_api = sub {
884 my ($param, $sync) = @_;
886 my $rpcenv = PVE
::RPCEnvironment
::get
();
888 my $authuser = $rpcenv->get_user();
890 my $node = extract_param
($param, 'node');
892 my $vmid = extract_param
($param, 'vmid');
894 my $digest = extract_param
($param, 'digest');
896 my $background_delay = extract_param
($param, 'background_delay');
898 my @paramarr = (); # used for log message
899 foreach my $key (keys %$param) {
900 push @paramarr, "-$key", $param->{$key};
903 my $skiplock = extract_param
($param, 'skiplock');
904 raise_param_exc
({ skiplock
=> "Only root may use this option." })
905 if $skiplock && $authuser ne 'root@pam';
907 my $delete_str = extract_param
($param, 'delete');
909 my $revert_str = extract_param
($param, 'revert');
911 my $force = extract_param
($param, 'force');
913 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
915 my $storecfg = PVE
::Storage
::config
();
917 my $defaults = PVE
::QemuServer
::load_defaults
();
919 &$resolve_cdrom_alias($param);
921 # now try to verify all parameters
924 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
925 if (!PVE
::QemuServer
::option_exists
($opt)) {
926 raise_param_exc
({ revert
=> "unknown option '$opt'" });
929 raise_param_exc
({ delete => "you can't use '-$opt' and " .
930 "-revert $opt' at the same time" })
931 if defined($param->{$opt});
937 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
938 $opt = 'ide2' if $opt eq 'cdrom';
940 raise_param_exc
({ delete => "you can't use '-$opt' and " .
941 "-delete $opt' at the same time" })
942 if defined($param->{$opt});
944 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
945 "-revert $opt' at the same time" })
948 if (!PVE
::QemuServer
::option_exists
($opt)) {
949 raise_param_exc
({ delete => "unknown option '$opt'" });
955 foreach my $opt (keys %$param) {
956 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
958 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
959 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
960 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
961 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
962 } elsif ($opt =~ m/^net(\d+)$/) {
964 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
965 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
969 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
971 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
973 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
977 my $conf = PVE
::QemuConfig-
>load_config($vmid);
979 die "checksum missmatch (file change by other user?)\n"
980 if $digest && $digest ne $conf->{digest
};
982 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
984 foreach my $opt (keys %$revert) {
985 if (defined($conf->{$opt})) {
986 $param->{$opt} = $conf->{$opt};
987 } elsif (defined($conf->{pending
}->{$opt})) {
992 if ($param->{memory
} || defined($param->{balloon
})) {
993 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
994 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
996 die "balloon value too large (must be smaller than assigned memory)\n"
997 if $balloon && $balloon > $maxmem;
1000 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1004 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1006 # write updates to pending section
1008 my $modified = {}; # record what $option we modify
1010 foreach my $opt (@delete) {
1011 $modified->{$opt} = 1;
1012 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1013 if (!defined($conf->{$opt})) {
1014 warn "cannot delete '$opt' - not set in current configuration!\n";
1015 $modified->{$opt} = 0;
1019 if ($opt =~ m/^unused/) {
1020 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1021 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1022 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1023 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1024 delete $conf->{$opt};
1025 PVE
::QemuConfig-
>write_config($vmid, $conf);
1027 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1028 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1029 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1030 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1031 if defined($conf->{pending
}->{$opt});
1032 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1033 PVE
::QemuConfig-
>write_config($vmid, $conf);
1035 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1036 PVE
::QemuConfig-
>write_config($vmid, $conf);
1040 foreach my $opt (keys %$param) { # add/change
1041 $modified->{$opt} = 1;
1042 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1043 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1045 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1046 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1047 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1048 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1050 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1052 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1053 if defined($conf->{pending
}->{$opt});
1055 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1056 } elsif ($opt eq "replicate") {
1057 # check if all volumes have replicate feature
1058 PVE
::QemuConfig-
>get_replicatable_volumes($storecfg, $conf);
1059 my $repl = PVE
::JSONSchema
::check_format
('pve-replicate', $param->{opt
});
1060 PVE
::Cluster
::check_node_exists
($repl->{target
});
1061 $conf->{$opt} = $param->{$opt};
1063 $conf->{pending
}->{$opt} = $param->{$opt};
1065 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1066 PVE
::QemuConfig-
>write_config($vmid, $conf);
1069 # remove pending changes when nothing changed
1070 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1071 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1072 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1074 return if !scalar(keys %{$conf->{pending
}});
1076 my $running = PVE
::QemuServer
::check_running
($vmid);
1078 # apply pending changes
1080 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1084 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1085 raise_param_exc
($errors) if scalar(keys %$errors);
1087 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1097 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1099 if ($background_delay) {
1101 # Note: It would be better to do that in the Event based HTTPServer
1102 # to avoid blocking call to sleep.
1104 my $end_time = time() + $background_delay;
1106 my $task = PVE
::Tools
::upid_decode
($upid);
1109 while (time() < $end_time) {
1110 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1112 sleep(1); # this gets interrupted when child process ends
1116 my $status = PVE
::Tools
::upid_read_status
($upid);
1117 return undef if $status eq 'OK';
1126 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1129 my $vm_config_perm_list = [
1134 'VM.Config.Network',
1136 'VM.Config.Options',
1139 __PACKAGE__-
>register_method({
1140 name
=> 'update_vm_async',
1141 path
=> '{vmid}/config',
1145 description
=> "Set virtual machine options (asynchrounous API).",
1147 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1150 additionalProperties
=> 0,
1151 properties
=> PVE
::QemuServer
::json_config_properties
(
1153 node
=> get_standard_option
('pve-node'),
1154 vmid
=> get_standard_option
('pve-vmid'),
1155 skiplock
=> get_standard_option
('skiplock'),
1157 type
=> 'string', format
=> 'pve-configid-list',
1158 description
=> "A list of settings you want to delete.",
1162 type
=> 'string', format
=> 'pve-configid-list',
1163 description
=> "Revert a pending change.",
1168 description
=> $opt_force_description,
1170 requires
=> 'delete',
1174 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1178 background_delay
=> {
1180 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1191 code
=> $update_vm_api,
1194 __PACKAGE__-
>register_method({
1195 name
=> 'update_vm',
1196 path
=> '{vmid}/config',
1200 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1202 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1205 additionalProperties
=> 0,
1206 properties
=> PVE
::QemuServer
::json_config_properties
(
1208 node
=> get_standard_option
('pve-node'),
1209 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1210 skiplock
=> get_standard_option
('skiplock'),
1212 type
=> 'string', format
=> 'pve-configid-list',
1213 description
=> "A list of settings you want to delete.",
1217 type
=> 'string', format
=> 'pve-configid-list',
1218 description
=> "Revert a pending change.",
1223 description
=> $opt_force_description,
1225 requires
=> 'delete',
1229 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1235 returns
=> { type
=> 'null' },
1238 &$update_vm_api($param, 1);
1244 __PACKAGE__-
>register_method({
1245 name
=> 'destroy_vm',
1250 description
=> "Destroy the vm (also delete all used/owned volumes).",
1252 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1255 additionalProperties
=> 0,
1257 node
=> get_standard_option
('pve-node'),
1258 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1259 skiplock
=> get_standard_option
('skiplock'),
1268 my $rpcenv = PVE
::RPCEnvironment
::get
();
1270 my $authuser = $rpcenv->get_user();
1272 my $vmid = $param->{vmid
};
1274 my $skiplock = $param->{skiplock
};
1275 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1276 if $skiplock && $authuser ne 'root@pam';
1279 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1281 my $storecfg = PVE
::Storage
::config
();
1283 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1285 die "unable to remove VM $vmid - used in HA resources\n"
1286 if PVE
::HA
::Config
::vm_is_ha_managed
($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(
1439 GetAddrInfoFlags
=> 0,
1440 ) or die "failed to create socket: $!\n";
1441 # Inside the worker we shouldn't have any previous alarms
1442 # running anyway...:
1444 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1446 accept(my $cli, $sock) or die "connection failed: $!\n";
1449 if (PVE
::Tools
::run_command
($cmd,
1450 output
=> '>&'.fileno($cli),
1451 input
=> '<&'.fileno($cli),
1454 die "Failed to run vncproxy.\n";
1461 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1463 PVE
::Tools
::wait_for_vnc_port
($port);
1474 __PACKAGE__-
>register_method({
1475 name
=> 'vncwebsocket',
1476 path
=> '{vmid}/vncwebsocket',
1479 description
=> "You also need to pass a valid ticket (vncticket).",
1480 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1482 description
=> "Opens a weksocket for VNC traffic.",
1484 additionalProperties
=> 0,
1486 node
=> get_standard_option
('pve-node'),
1487 vmid
=> get_standard_option
('pve-vmid'),
1489 description
=> "Ticket from previous call to vncproxy.",
1494 description
=> "Port number returned by previous vncproxy call.",
1504 port
=> { type
=> 'string' },
1510 my $rpcenv = PVE
::RPCEnvironment
::get
();
1512 my $authuser = $rpcenv->get_user();
1514 my $vmid = $param->{vmid
};
1515 my $node = $param->{node
};
1517 my $authpath = "/vms/$vmid";
1519 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1521 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1523 # Note: VNC ports are acessible from outside, so we do not gain any
1524 # security if we verify that $param->{port} belongs to VM $vmid. This
1525 # check is done by verifying the VNC ticket (inside VNC protocol).
1527 my $port = $param->{port
};
1529 return { port
=> $port };
1532 __PACKAGE__-
>register_method({
1533 name
=> 'spiceproxy',
1534 path
=> '{vmid}/spiceproxy',
1539 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1541 description
=> "Returns a SPICE configuration to connect to the VM.",
1543 additionalProperties
=> 0,
1545 node
=> get_standard_option
('pve-node'),
1546 vmid
=> get_standard_option
('pve-vmid'),
1547 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1550 returns
=> get_standard_option
('remote-viewer-config'),
1554 my $rpcenv = PVE
::RPCEnvironment
::get
();
1556 my $authuser = $rpcenv->get_user();
1558 my $vmid = $param->{vmid
};
1559 my $node = $param->{node
};
1560 my $proxy = $param->{proxy
};
1562 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1563 my $title = "VM $vmid";
1564 $title .= " - ". $conf->{name
} if $conf->{name
};
1566 my $port = PVE
::QemuServer
::spice_port
($vmid);
1568 my ($ticket, undef, $remote_viewer_config) =
1569 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1571 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1572 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1574 return $remote_viewer_config;
1577 __PACKAGE__-
>register_method({
1579 path
=> '{vmid}/status',
1582 description
=> "Directory index",
1587 additionalProperties
=> 0,
1589 node
=> get_standard_option
('pve-node'),
1590 vmid
=> get_standard_option
('pve-vmid'),
1598 subdir
=> { type
=> 'string' },
1601 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1607 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1610 { subdir
=> 'current' },
1611 { subdir
=> 'start' },
1612 { subdir
=> 'stop' },
1618 __PACKAGE__-
>register_method({
1619 name
=> 'vm_status',
1620 path
=> '{vmid}/status/current',
1623 protected
=> 1, # qemu pid files are only readable by root
1624 description
=> "Get virtual machine status.",
1626 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1629 additionalProperties
=> 0,
1631 node
=> get_standard_option
('pve-node'),
1632 vmid
=> get_standard_option
('pve-vmid'),
1635 returns
=> { type
=> 'object' },
1640 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1642 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1643 my $status = $vmstatus->{$param->{vmid
}};
1645 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1647 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1652 __PACKAGE__-
>register_method({
1654 path
=> '{vmid}/status/start',
1658 description
=> "Start virtual machine.",
1660 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1663 additionalProperties
=> 0,
1665 node
=> get_standard_option
('pve-node'),
1666 vmid
=> get_standard_option
('pve-vmid',
1667 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1668 skiplock
=> get_standard_option
('skiplock'),
1669 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1670 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1673 enum
=> ['secure', 'insecure'],
1674 description
=> "Migration traffic is encrypted using an SSH " .
1675 "tunnel by default. On secure, completely private networks " .
1676 "this can be disabled to increase performance.",
1679 migration_network
=> {
1680 type
=> 'string', format
=> 'CIDR',
1681 description
=> "CIDR of the (sub) network that is used for migration.",
1684 machine
=> get_standard_option
('pve-qm-machine'),
1686 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1698 my $rpcenv = PVE
::RPCEnvironment
::get
();
1700 my $authuser = $rpcenv->get_user();
1702 my $node = extract_param
($param, 'node');
1704 my $vmid = extract_param
($param, 'vmid');
1706 my $machine = extract_param
($param, 'machine');
1708 my $stateuri = extract_param
($param, 'stateuri');
1709 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1710 if $stateuri && $authuser ne 'root@pam';
1712 my $skiplock = extract_param
($param, 'skiplock');
1713 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1714 if $skiplock && $authuser ne 'root@pam';
1716 my $migratedfrom = extract_param
($param, 'migratedfrom');
1717 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1718 if $migratedfrom && $authuser ne 'root@pam';
1720 my $migration_type = extract_param
($param, 'migration_type');
1721 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1722 if $migration_type && $authuser ne 'root@pam';
1724 my $migration_network = extract_param
($param, 'migration_network');
1725 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1726 if $migration_network && $authuser ne 'root@pam';
1728 my $targetstorage = extract_param
($param, 'targetstorage');
1729 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1730 if $targetstorage && $authuser ne 'root@pam';
1732 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1733 if $targetstorage && !$migratedfrom;
1735 # read spice ticket from STDIN
1737 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1738 if (defined(my $line = <>)) {
1740 $spice_ticket = $line;
1744 PVE
::Cluster
::check_cfs_quorum
();
1746 my $storecfg = PVE
::Storage
::config
();
1748 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1749 $rpcenv->{type
} ne 'ha') {
1754 my $service = "vm:$vmid";
1756 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1758 print "Executing HA start for VM $vmid\n";
1760 PVE
::Tools
::run_command
($cmd);
1765 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1772 syslog
('info', "start VM $vmid: $upid\n");
1774 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1775 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1780 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1784 __PACKAGE__-
>register_method({
1786 path
=> '{vmid}/status/stop',
1790 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1791 "is akin to pulling the power plug of a running computer and may damage the VM data",
1793 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1796 additionalProperties
=> 0,
1798 node
=> get_standard_option
('pve-node'),
1799 vmid
=> get_standard_option
('pve-vmid',
1800 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1801 skiplock
=> get_standard_option
('skiplock'),
1802 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1804 description
=> "Wait maximal timeout seconds.",
1810 description
=> "Do not deactivate storage volumes.",
1823 my $rpcenv = PVE
::RPCEnvironment
::get
();
1825 my $authuser = $rpcenv->get_user();
1827 my $node = extract_param
($param, 'node');
1829 my $vmid = extract_param
($param, 'vmid');
1831 my $skiplock = extract_param
($param, 'skiplock');
1832 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1833 if $skiplock && $authuser ne 'root@pam';
1835 my $keepActive = extract_param
($param, 'keepActive');
1836 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1837 if $keepActive && $authuser ne 'root@pam';
1839 my $migratedfrom = extract_param
($param, 'migratedfrom');
1840 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1841 if $migratedfrom && $authuser ne 'root@pam';
1844 my $storecfg = PVE
::Storage
::config
();
1846 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1851 my $service = "vm:$vmid";
1853 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1855 print "Executing HA stop for VM $vmid\n";
1857 PVE
::Tools
::run_command
($cmd);
1862 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1868 syslog
('info', "stop VM $vmid: $upid\n");
1870 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1871 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1876 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1880 __PACKAGE__-
>register_method({
1882 path
=> '{vmid}/status/reset',
1886 description
=> "Reset virtual machine.",
1888 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1891 additionalProperties
=> 0,
1893 node
=> get_standard_option
('pve-node'),
1894 vmid
=> get_standard_option
('pve-vmid',
1895 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1896 skiplock
=> get_standard_option
('skiplock'),
1905 my $rpcenv = PVE
::RPCEnvironment
::get
();
1907 my $authuser = $rpcenv->get_user();
1909 my $node = extract_param
($param, 'node');
1911 my $vmid = extract_param
($param, 'vmid');
1913 my $skiplock = extract_param
($param, 'skiplock');
1914 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1915 if $skiplock && $authuser ne 'root@pam';
1917 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1922 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1927 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1930 __PACKAGE__-
>register_method({
1931 name
=> 'vm_shutdown',
1932 path
=> '{vmid}/status/shutdown',
1936 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1937 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1939 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1942 additionalProperties
=> 0,
1944 node
=> get_standard_option
('pve-node'),
1945 vmid
=> get_standard_option
('pve-vmid',
1946 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1947 skiplock
=> get_standard_option
('skiplock'),
1949 description
=> "Wait maximal timeout seconds.",
1955 description
=> "Make sure the VM stops.",
1961 description
=> "Do not deactivate storage volumes.",
1974 my $rpcenv = PVE
::RPCEnvironment
::get
();
1976 my $authuser = $rpcenv->get_user();
1978 my $node = extract_param
($param, 'node');
1980 my $vmid = extract_param
($param, 'vmid');
1982 my $skiplock = extract_param
($param, 'skiplock');
1983 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1984 if $skiplock && $authuser ne 'root@pam';
1986 my $keepActive = extract_param
($param, 'keepActive');
1987 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1988 if $keepActive && $authuser ne 'root@pam';
1990 my $storecfg = PVE
::Storage
::config
();
1994 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1995 # otherwise, we will infer a shutdown command, but run into the timeout,
1996 # then when the vm is resumed, it will instantly shutdown
1998 # checking the qmp status here to get feedback to the gui/cli/api
1999 # and the status query should not take too long
2002 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2006 if (!$err && $qmpstatus->{status
} eq "paused") {
2007 if ($param->{forceStop
}) {
2008 warn "VM is paused - stop instead of shutdown\n";
2011 die "VM is paused - cannot shutdown\n";
2015 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2016 ($rpcenv->{type
} ne 'ha')) {
2021 my $service = "vm:$vmid";
2023 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2025 print "Executing HA stop for VM $vmid\n";
2027 PVE
::Tools
::run_command
($cmd);
2032 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2039 syslog
('info', "shutdown VM $vmid: $upid\n");
2041 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2042 $shutdown, $param->{forceStop
}, $keepActive);
2047 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2051 __PACKAGE__-
>register_method({
2052 name
=> 'vm_suspend',
2053 path
=> '{vmid}/status/suspend',
2057 description
=> "Suspend virtual machine.",
2059 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2062 additionalProperties
=> 0,
2064 node
=> get_standard_option
('pve-node'),
2065 vmid
=> get_standard_option
('pve-vmid',
2066 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2067 skiplock
=> get_standard_option
('skiplock'),
2076 my $rpcenv = PVE
::RPCEnvironment
::get
();
2078 my $authuser = $rpcenv->get_user();
2080 my $node = extract_param
($param, 'node');
2082 my $vmid = extract_param
($param, 'vmid');
2084 my $skiplock = extract_param
($param, 'skiplock');
2085 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2086 if $skiplock && $authuser ne 'root@pam';
2088 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2093 syslog
('info', "suspend VM $vmid: $upid\n");
2095 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2100 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2103 __PACKAGE__-
>register_method({
2104 name
=> 'vm_resume',
2105 path
=> '{vmid}/status/resume',
2109 description
=> "Resume virtual machine.",
2111 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2114 additionalProperties
=> 0,
2116 node
=> get_standard_option
('pve-node'),
2117 vmid
=> get_standard_option
('pve-vmid',
2118 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2119 skiplock
=> get_standard_option
('skiplock'),
2120 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2130 my $rpcenv = PVE
::RPCEnvironment
::get
();
2132 my $authuser = $rpcenv->get_user();
2134 my $node = extract_param
($param, 'node');
2136 my $vmid = extract_param
($param, 'vmid');
2138 my $skiplock = extract_param
($param, 'skiplock');
2139 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2140 if $skiplock && $authuser ne 'root@pam';
2142 my $nocheck = extract_param
($param, 'nocheck');
2144 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2149 syslog
('info', "resume VM $vmid: $upid\n");
2151 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2156 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2159 __PACKAGE__-
>register_method({
2160 name
=> 'vm_sendkey',
2161 path
=> '{vmid}/sendkey',
2165 description
=> "Send key event to virtual machine.",
2167 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2170 additionalProperties
=> 0,
2172 node
=> get_standard_option
('pve-node'),
2173 vmid
=> get_standard_option
('pve-vmid',
2174 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2175 skiplock
=> get_standard_option
('skiplock'),
2177 description
=> "The key (qemu monitor encoding).",
2182 returns
=> { type
=> 'null'},
2186 my $rpcenv = PVE
::RPCEnvironment
::get
();
2188 my $authuser = $rpcenv->get_user();
2190 my $node = extract_param
($param, 'node');
2192 my $vmid = extract_param
($param, 'vmid');
2194 my $skiplock = extract_param
($param, 'skiplock');
2195 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2196 if $skiplock && $authuser ne 'root@pam';
2198 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2203 __PACKAGE__-
>register_method({
2204 name
=> 'vm_feature',
2205 path
=> '{vmid}/feature',
2209 description
=> "Check if feature for virtual machine is available.",
2211 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2214 additionalProperties
=> 0,
2216 node
=> get_standard_option
('pve-node'),
2217 vmid
=> get_standard_option
('pve-vmid'),
2219 description
=> "Feature to check.",
2221 enum
=> [ 'snapshot', 'clone', 'copy' ],
2223 snapname
=> get_standard_option
('pve-snapshot-name', {
2231 hasFeature
=> { type
=> 'boolean' },
2234 items
=> { type
=> 'string' },
2241 my $node = extract_param
($param, 'node');
2243 my $vmid = extract_param
($param, 'vmid');
2245 my $snapname = extract_param
($param, 'snapname');
2247 my $feature = extract_param
($param, 'feature');
2249 my $running = PVE
::QemuServer
::check_running
($vmid);
2251 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2254 my $snap = $conf->{snapshots
}->{$snapname};
2255 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2258 my $storecfg = PVE
::Storage
::config
();
2260 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2261 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2264 hasFeature
=> $hasFeature,
2265 nodes
=> [ keys %$nodelist ],
2269 __PACKAGE__-
>register_method({
2271 path
=> '{vmid}/clone',
2275 description
=> "Create a copy of virtual machine/template.",
2277 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2278 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2279 "'Datastore.AllocateSpace' on any used storage.",
2282 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2284 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2285 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2290 additionalProperties
=> 0,
2292 node
=> get_standard_option
('pve-node'),
2293 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2294 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2297 type
=> 'string', format
=> 'dns-name',
2298 description
=> "Set a name for the new VM.",
2303 description
=> "Description for the new VM.",
2307 type
=> 'string', format
=> 'pve-poolid',
2308 description
=> "Add the new VM to the specified pool.",
2310 snapname
=> get_standard_option
('pve-snapshot-name', {
2313 storage
=> get_standard_option
('pve-storage-id', {
2314 description
=> "Target storage for full clone.",
2319 description
=> "Target format for file storage.",
2323 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2328 description
=> "Create a full copy of all disk. This is always done when " .
2329 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2332 target
=> get_standard_option
('pve-node', {
2333 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2344 my $rpcenv = PVE
::RPCEnvironment
::get
();
2346 my $authuser = $rpcenv->get_user();
2348 my $node = extract_param
($param, 'node');
2350 my $vmid = extract_param
($param, 'vmid');
2352 my $newid = extract_param
($param, 'newid');
2354 my $pool = extract_param
($param, 'pool');
2356 if (defined($pool)) {
2357 $rpcenv->check_pool_exist($pool);
2360 my $snapname = extract_param
($param, 'snapname');
2362 my $storage = extract_param
($param, 'storage');
2364 my $format = extract_param
($param, 'format');
2366 my $target = extract_param
($param, 'target');
2368 my $localnode = PVE
::INotify
::nodename
();
2370 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2372 PVE
::Cluster
::check_node_exists
($target) if $target;
2374 my $storecfg = PVE
::Storage
::config
();
2377 # check if storage is enabled on local node
2378 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2380 # check if storage is available on target node
2381 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2382 # clone only works if target storage is shared
2383 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2384 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2388 PVE
::Cluster
::check_cfs_quorum
();
2390 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2392 # exclusive lock if VM is running - else shared lock is enough;
2393 my $shared_lock = $running ?
0 : 1;
2397 # do all tests after lock
2398 # we also try to do all tests before we fork the worker
2400 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2402 PVE
::QemuConfig-
>check_lock($conf);
2404 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2406 die "unexpected state change\n" if $verify_running != $running;
2408 die "snapshot '$snapname' does not exist\n"
2409 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2411 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2413 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2415 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2417 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2419 die "unable to create VM $newid: config file already exists\n"
2422 my $newconf = { lock => 'clone' };
2427 foreach my $opt (keys %$oldconf) {
2428 my $value = $oldconf->{$opt};
2430 # do not copy snapshot related info
2431 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2432 $opt eq 'vmstate' || $opt eq 'snapstate';
2434 # no need to copy unused images, because VMID(owner) changes anyways
2435 next if $opt =~ m/^unused\d+$/;
2437 # always change MAC! address
2438 if ($opt =~ m/^net(\d+)$/) {
2439 my $net = PVE
::QemuServer
::parse_net
($value);
2440 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2441 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2442 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2443 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2444 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2445 die "unable to parse drive options for '$opt'\n" if !$drive;
2446 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2447 $newconf->{$opt} = $value; # simply copy configuration
2449 if ($param->{full
}) {
2450 die "Full clone feature is not available"
2451 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2452 $fullclone->{$opt} = 1;
2454 # not full means clone instead of copy
2455 die "Linked clone feature is not available"
2456 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2458 $drives->{$opt} = $drive;
2459 push @$vollist, $drive->{file
};
2462 # copy everything else
2463 $newconf->{$opt} = $value;
2467 # auto generate a new uuid
2468 my ($uuid, $uuid_str);
2469 UUID
::generate
($uuid);
2470 UUID
::unparse
($uuid, $uuid_str);
2471 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2472 $smbios1->{uuid
} = $uuid_str;
2473 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2475 delete $newconf->{template
};
2477 if ($param->{name
}) {
2478 $newconf->{name
} = $param->{name
};
2480 if ($oldconf->{name
}) {
2481 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2483 $newconf->{name
} = "Copy-of-VM-$vmid";
2487 if ($param->{description
}) {
2488 $newconf->{description
} = $param->{description
};
2491 # create empty/temp config - this fails if VM already exists on other node
2492 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2497 my $newvollist = [];
2501 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2503 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2505 my $total_jobs = scalar(keys %{$drives});
2508 foreach my $opt (keys %$drives) {
2509 my $drive = $drives->{$opt};
2510 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2512 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2513 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2514 $jobs, $skipcomplete, $oldconf->{agent
});
2516 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2518 PVE
::QemuConfig-
>write_config($newid, $newconf);
2522 delete $newconf->{lock};
2523 PVE
::QemuConfig-
>write_config($newid, $newconf);
2526 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2527 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2528 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2530 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2531 die "Failed to move config to node '$target' - rename failed: $!\n"
2532 if !rename($conffile, $newconffile);
2535 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2540 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2542 sleep 1; # some storage like rbd need to wait before release volume - really?
2544 foreach my $volid (@$newvollist) {
2545 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2548 die "clone failed: $err";
2554 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2556 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2559 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2560 # Aquire exclusive lock lock for $newid
2561 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2566 __PACKAGE__-
>register_method({
2567 name
=> 'move_vm_disk',
2568 path
=> '{vmid}/move_disk',
2572 description
=> "Move volume to different storage.",
2574 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2576 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2577 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2581 additionalProperties
=> 0,
2583 node
=> get_standard_option
('pve-node'),
2584 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2587 description
=> "The disk you want to move.",
2588 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2590 storage
=> get_standard_option
('pve-storage-id', {
2591 description
=> "Target storage.",
2592 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2596 description
=> "Target Format.",
2597 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2602 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2608 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2616 description
=> "the task ID.",
2621 my $rpcenv = PVE
::RPCEnvironment
::get
();
2623 my $authuser = $rpcenv->get_user();
2625 my $node = extract_param
($param, 'node');
2627 my $vmid = extract_param
($param, 'vmid');
2629 my $digest = extract_param
($param, 'digest');
2631 my $disk = extract_param
($param, 'disk');
2633 my $storeid = extract_param
($param, 'storage');
2635 my $format = extract_param
($param, 'format');
2637 my $storecfg = PVE
::Storage
::config
();
2639 my $updatefn = sub {
2641 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2643 PVE
::QemuConfig-
>check_lock($conf);
2645 die "checksum missmatch (file change by other user?)\n"
2646 if $digest && $digest ne $conf->{digest
};
2648 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2650 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2652 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2654 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2657 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2658 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2662 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2663 (!$format || !$oldfmt || $oldfmt eq $format);
2665 # this only checks snapshots because $disk is passed!
2666 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2667 die "you can't move a disk with snapshots and delete the source\n"
2668 if $snapshotted && $param->{delete};
2670 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2672 my $running = PVE
::QemuServer
::check_running
($vmid);
2674 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2678 my $newvollist = [];
2681 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2683 warn "moving disk with snapshots, snapshots will not be moved!\n"
2686 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2687 $vmid, $storeid, $format, 1, $newvollist);
2689 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2691 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2693 # convert moved disk to base if part of template
2694 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2695 if PVE
::QemuConfig-
>is_template($conf);
2697 PVE
::QemuConfig-
>write_config($vmid, $conf);
2700 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2701 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2708 foreach my $volid (@$newvollist) {
2709 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2712 die "storage migration failed: $err";
2715 if ($param->{delete}) {
2717 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2718 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2724 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2727 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2730 __PACKAGE__-
>register_method({
2731 name
=> 'migrate_vm',
2732 path
=> '{vmid}/migrate',
2736 description
=> "Migrate virtual machine. Creates a new migration task.",
2738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2741 additionalProperties
=> 0,
2743 node
=> get_standard_option
('pve-node'),
2744 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2745 target
=> get_standard_option
('pve-node', {
2746 description
=> "Target node.",
2747 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2751 description
=> "Use online/live migration.",
2756 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2761 enum
=> ['secure', 'insecure'],
2762 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2765 migration_network
=> {
2766 type
=> 'string', format
=> 'CIDR',
2767 description
=> "CIDR of the (sub) network that is used for migration.",
2770 "with-local-disks" => {
2772 description
=> "Enable live storage migration for local disk",
2775 targetstorage
=> get_standard_option
('pve-storage-id', {
2776 description
=> "Default target storage.",
2778 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2784 description
=> "the task ID.",
2789 my $rpcenv = PVE
::RPCEnvironment
::get
();
2791 my $authuser = $rpcenv->get_user();
2793 my $target = extract_param
($param, 'target');
2795 my $localnode = PVE
::INotify
::nodename
();
2796 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2798 PVE
::Cluster
::check_cfs_quorum
();
2800 PVE
::Cluster
::check_node_exists
($target);
2802 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2804 my $vmid = extract_param
($param, 'vmid');
2806 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2807 if !$param->{online
} && $param->{targetstorage
};
2809 raise_param_exc
({ force
=> "Only root may use this option." })
2810 if $param->{force
} && $authuser ne 'root@pam';
2812 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2813 if $param->{migration_type
} && $authuser ne 'root@pam';
2815 # allow root only until better network permissions are available
2816 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2817 if $param->{migration_network
} && $authuser ne 'root@pam';
2820 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2822 # try to detect errors early
2824 PVE
::QemuConfig-
>check_lock($conf);
2826 if (PVE
::QemuServer
::check_running
($vmid)) {
2827 die "cant migrate running VM without --online\n"
2828 if !$param->{online
};
2831 my $storecfg = PVE
::Storage
::config
();
2833 if( $param->{targetstorage
}) {
2834 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2836 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2839 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2844 my $service = "vm:$vmid";
2846 my $cmd = ['ha-manager', 'migrate', $service, $target];
2848 print "Executing HA migrate for VM $vmid to node $target\n";
2850 PVE
::Tools
::run_command
($cmd);
2855 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2862 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2865 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2870 __PACKAGE__-
>register_method({
2872 path
=> '{vmid}/monitor',
2876 description
=> "Execute Qemu monitor commands.",
2878 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2879 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2882 additionalProperties
=> 0,
2884 node
=> get_standard_option
('pve-node'),
2885 vmid
=> get_standard_option
('pve-vmid'),
2888 description
=> "The monitor command.",
2892 returns
=> { type
=> 'string'},
2896 my $rpcenv = PVE
::RPCEnvironment
::get
();
2897 my $authuser = $rpcenv->get_user();
2900 my $command = shift;
2901 return $command =~ m/^\s*info(\s+|$)/
2902 || $command =~ m/^\s*help\s*$/;
2905 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2906 if !&$is_ro($param->{command
});
2908 my $vmid = $param->{vmid
};
2910 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2914 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2916 $res = "ERROR: $@" if $@;
2921 my $guest_agent_commands = [
2929 'network-get-interfaces',
2932 'get-memory-blocks',
2933 'get-memory-block-info',
2940 __PACKAGE__-
>register_method({
2942 path
=> '{vmid}/agent',
2946 description
=> "Execute Qemu Guest Agent commands.",
2948 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2951 additionalProperties
=> 0,
2953 node
=> get_standard_option
('pve-node'),
2954 vmid
=> get_standard_option
('pve-vmid', {
2955 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2958 description
=> "The QGA command.",
2959 enum
=> $guest_agent_commands,
2965 description
=> "Returns an object with a single `result` property. The type of that
2966 property depends on the executed command.",
2971 my $vmid = $param->{vmid
};
2973 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2975 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2976 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2978 my $cmd = $param->{command
};
2980 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2982 return { result
=> $res };
2985 __PACKAGE__-
>register_method({
2986 name
=> 'resize_vm',
2987 path
=> '{vmid}/resize',
2991 description
=> "Extend volume size.",
2993 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2996 additionalProperties
=> 0,
2998 node
=> get_standard_option
('pve-node'),
2999 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3000 skiplock
=> get_standard_option
('skiplock'),
3003 description
=> "The disk you want to resize.",
3004 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3008 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3009 description
=> "The new size. With the `+` sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
3013 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3019 returns
=> { type
=> 'null'},
3023 my $rpcenv = PVE
::RPCEnvironment
::get
();
3025 my $authuser = $rpcenv->get_user();
3027 my $node = extract_param
($param, 'node');
3029 my $vmid = extract_param
($param, 'vmid');
3031 my $digest = extract_param
($param, 'digest');
3033 my $disk = extract_param
($param, 'disk');
3035 my $sizestr = extract_param
($param, 'size');
3037 my $skiplock = extract_param
($param, 'skiplock');
3038 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3039 if $skiplock && $authuser ne 'root@pam';
3041 my $storecfg = PVE
::Storage
::config
();
3043 my $updatefn = sub {
3045 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3047 die "checksum missmatch (file change by other user?)\n"
3048 if $digest && $digest ne $conf->{digest
};
3049 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3051 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3053 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3055 my (undef, undef, undef, undef, undef, undef, $format) =
3056 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3058 die "can't resize volume: $disk if snapshot exists\n"
3059 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3061 my $volid = $drive->{file
};
3063 die "disk '$disk' has no associated volume\n" if !$volid;
3065 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3067 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3069 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3071 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3072 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3074 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3075 my ($ext, $newsize, $unit) = ($1, $2, $4);
3078 $newsize = $newsize * 1024;
3079 } elsif ($unit eq 'M') {
3080 $newsize = $newsize * 1024 * 1024;
3081 } elsif ($unit eq 'G') {
3082 $newsize = $newsize * 1024 * 1024 * 1024;
3083 } elsif ($unit eq 'T') {
3084 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3087 $newsize += $size if $ext;
3088 $newsize = int($newsize);
3090 die "shrinking disks is not supported\n" if $newsize < $size;
3092 return if $size == $newsize;
3094 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3096 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3098 $drive->{size
} = $newsize;
3099 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3101 PVE
::QemuConfig-
>write_config($vmid, $conf);
3104 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3108 __PACKAGE__-
>register_method({
3109 name
=> 'snapshot_list',
3110 path
=> '{vmid}/snapshot',
3112 description
=> "List all snapshots.",
3114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3117 protected
=> 1, # qemu pid files are only readable by root
3119 additionalProperties
=> 0,
3121 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3122 node
=> get_standard_option
('pve-node'),
3131 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3136 my $vmid = $param->{vmid
};
3138 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3139 my $snaphash = $conf->{snapshots
} || {};
3143 foreach my $name (keys %$snaphash) {
3144 my $d = $snaphash->{$name};
3147 snaptime
=> $d->{snaptime
} || 0,
3148 vmstate
=> $d->{vmstate
} ?
1 : 0,
3149 description
=> $d->{description
} || '',
3151 $item->{parent
} = $d->{parent
} if $d->{parent
};
3152 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3156 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3157 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3158 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3160 push @$res, $current;
3165 __PACKAGE__-
>register_method({
3167 path
=> '{vmid}/snapshot',
3171 description
=> "Snapshot a VM.",
3173 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3176 additionalProperties
=> 0,
3178 node
=> get_standard_option
('pve-node'),
3179 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3180 snapname
=> get_standard_option
('pve-snapshot-name'),
3184 description
=> "Save the vmstate",
3189 description
=> "A textual description or comment.",
3195 description
=> "the task ID.",
3200 my $rpcenv = PVE
::RPCEnvironment
::get
();
3202 my $authuser = $rpcenv->get_user();
3204 my $node = extract_param
($param, 'node');
3206 my $vmid = extract_param
($param, 'vmid');
3208 my $snapname = extract_param
($param, 'snapname');
3210 die "unable to use snapshot name 'current' (reserved name)\n"
3211 if $snapname eq 'current';
3214 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3215 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3216 $param->{description
});
3219 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3222 __PACKAGE__-
>register_method({
3223 name
=> 'snapshot_cmd_idx',
3224 path
=> '{vmid}/snapshot/{snapname}',
3231 additionalProperties
=> 0,
3233 vmid
=> get_standard_option
('pve-vmid'),
3234 node
=> get_standard_option
('pve-node'),
3235 snapname
=> get_standard_option
('pve-snapshot-name'),
3244 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3251 push @$res, { cmd
=> 'rollback' };
3252 push @$res, { cmd
=> 'config' };
3257 __PACKAGE__-
>register_method({
3258 name
=> 'update_snapshot_config',
3259 path
=> '{vmid}/snapshot/{snapname}/config',
3263 description
=> "Update snapshot metadata.",
3265 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3268 additionalProperties
=> 0,
3270 node
=> get_standard_option
('pve-node'),
3271 vmid
=> get_standard_option
('pve-vmid'),
3272 snapname
=> get_standard_option
('pve-snapshot-name'),
3276 description
=> "A textual description or comment.",
3280 returns
=> { type
=> 'null' },
3284 my $rpcenv = PVE
::RPCEnvironment
::get
();
3286 my $authuser = $rpcenv->get_user();
3288 my $vmid = extract_param
($param, 'vmid');
3290 my $snapname = extract_param
($param, 'snapname');
3292 return undef if !defined($param->{description
});
3294 my $updatefn = sub {
3296 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3298 PVE
::QemuConfig-
>check_lock($conf);
3300 my $snap = $conf->{snapshots
}->{$snapname};
3302 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3304 $snap->{description
} = $param->{description
} if defined($param->{description
});
3306 PVE
::QemuConfig-
>write_config($vmid, $conf);
3309 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3314 __PACKAGE__-
>register_method({
3315 name
=> 'get_snapshot_config',
3316 path
=> '{vmid}/snapshot/{snapname}/config',
3319 description
=> "Get snapshot configuration",
3321 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3324 additionalProperties
=> 0,
3326 node
=> get_standard_option
('pve-node'),
3327 vmid
=> get_standard_option
('pve-vmid'),
3328 snapname
=> get_standard_option
('pve-snapshot-name'),
3331 returns
=> { type
=> "object" },
3335 my $rpcenv = PVE
::RPCEnvironment
::get
();
3337 my $authuser = $rpcenv->get_user();
3339 my $vmid = extract_param
($param, 'vmid');
3341 my $snapname = extract_param
($param, 'snapname');
3343 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3345 my $snap = $conf->{snapshots
}->{$snapname};
3347 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3352 __PACKAGE__-
>register_method({
3354 path
=> '{vmid}/snapshot/{snapname}/rollback',
3358 description
=> "Rollback VM state to specified snapshot.",
3360 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3363 additionalProperties
=> 0,
3365 node
=> get_standard_option
('pve-node'),
3366 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3367 snapname
=> get_standard_option
('pve-snapshot-name'),
3372 description
=> "the task ID.",
3377 my $rpcenv = PVE
::RPCEnvironment
::get
();
3379 my $authuser = $rpcenv->get_user();
3381 my $node = extract_param
($param, 'node');
3383 my $vmid = extract_param
($param, 'vmid');
3385 my $snapname = extract_param
($param, 'snapname');
3388 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3389 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3392 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3395 __PACKAGE__-
>register_method({
3396 name
=> 'delsnapshot',
3397 path
=> '{vmid}/snapshot/{snapname}',
3401 description
=> "Delete a VM snapshot.",
3403 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3406 additionalProperties
=> 0,
3408 node
=> get_standard_option
('pve-node'),
3409 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3410 snapname
=> get_standard_option
('pve-snapshot-name'),
3414 description
=> "For removal from config file, even if removing disk snapshots fails.",
3420 description
=> "the task ID.",
3425 my $rpcenv = PVE
::RPCEnvironment
::get
();
3427 my $authuser = $rpcenv->get_user();
3429 my $node = extract_param
($param, 'node');
3431 my $vmid = extract_param
($param, 'vmid');
3433 my $snapname = extract_param
($param, 'snapname');
3436 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3437 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3440 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3443 __PACKAGE__-
>register_method({
3445 path
=> '{vmid}/template',
3449 description
=> "Create a Template.",
3451 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3452 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3455 additionalProperties
=> 0,
3457 node
=> get_standard_option
('pve-node'),
3458 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3462 description
=> "If you want to convert only 1 disk to base image.",
3463 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3468 returns
=> { type
=> 'null'},
3472 my $rpcenv = PVE
::RPCEnvironment
::get
();
3474 my $authuser = $rpcenv->get_user();
3476 my $node = extract_param
($param, 'node');
3478 my $vmid = extract_param
($param, 'vmid');
3480 my $disk = extract_param
($param, 'disk');
3482 my $updatefn = sub {
3484 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3486 PVE
::QemuConfig-
>check_lock($conf);
3488 die "unable to create template, because VM contains snapshots\n"
3489 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3491 die "you can't convert a template to a template\n"
3492 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3494 die "you can't convert a VM to template if VM is running\n"
3495 if PVE
::QemuServer
::check_running
($vmid);
3498 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3501 $conf->{template
} = 1;
3502 PVE
::QemuConfig-
>write_config($vmid, $conf);
3504 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3507 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);