1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
21 use PVE
::RPCEnvironment
;
22 use PVE
::AccessControl
;
26 use PVE
::API2
::Firewall
::VM
;
27 use PVE
::ReplicationTools
;
30 if (!$ENV{PVE_GENERATING_DOCS
}) {
31 require PVE
::HA
::Env
::PVE2
;
32 import PVE
::HA
::Env
::PVE2
;
33 require PVE
::HA
::Config
;
34 import PVE
::HA
::Config
;
38 use Data
::Dumper
; # fixme: remove
40 use base
qw(PVE::RESTHandler);
42 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
44 my $resolve_cdrom_alias = sub {
47 if (my $value = $param->{cdrom
}) {
48 $value .= ",media=cdrom" if $value !~ m/media=/;
49 $param->{ide2
} = $value;
50 delete $param->{cdrom
};
54 my $check_storage_access = sub {
55 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
57 PVE
::QemuServer
::foreach_drive
($settings, sub {
58 my ($ds, $drive) = @_;
60 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
62 my $volid = $drive->{file
};
64 if (!$volid || $volid eq 'none') {
66 } elsif ($isCDROM && ($volid eq 'cdrom')) {
67 $rpcenv->check($authuser, "/", ['Sys.Console']);
68 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
69 my ($storeid, $size) = ($2 || $default_storage, $3);
70 die "no storage ID specified (and no default storage)\n" if !$storeid;
71 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
73 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
78 my $check_storage_access_clone = sub {
79 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
83 PVE
::QemuServer
::foreach_drive
($conf, sub {
84 my ($ds, $drive) = @_;
86 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
88 my $volid = $drive->{file
};
90 return if !$volid || $volid eq 'none';
93 if ($volid eq 'cdrom') {
94 $rpcenv->check($authuser, "/", ['Sys.Console']);
96 # we simply allow access
97 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
98 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
99 $sharedvm = 0 if !$scfg->{shared
};
103 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
104 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
105 $sharedvm = 0 if !$scfg->{shared
};
107 $sid = $storage if $storage;
108 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
115 # Note: $pool is only needed when creating a VM, because pool permissions
116 # are automatically inherited if VM already exists inside a pool.
117 my $create_disks = sub {
118 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
123 PVE
::QemuServer
::foreach_drive
($settings, sub {
124 my ($ds, $disk) = @_;
126 my $volid = $disk->{file
};
128 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
129 delete $disk->{size
};
130 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
131 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
132 my ($storeid, $size) = ($2 || $default_storage, $3);
133 die "no storage ID specified (and no default storage)\n" if !$storeid;
134 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
135 my $fmt = $disk->{format
} || $defformat;
138 if ($ds eq 'efidisk0') {
140 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
141 die "uefi vars image not found\n" if ! -f
$ovmfvars;
142 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
144 $disk->{file
} = $volid;
145 $disk->{size
} = 128*1024;
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
147 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
148 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
149 my $path = PVE
::Storage
::path
($storecfg, $volid);
150 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
151 push @$efidiskcmd, $ovmfvars;
152 push @$efidiskcmd, $path;
154 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
156 eval { PVE
::Tools
::run_command
($efidiskcmd); };
158 die "Copying of EFI Vars image failed: $err" if $err;
160 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
161 $fmt, undef, $size*1024*1024);
162 $disk->{file
} = $volid;
163 $disk->{size
} = $size*1024*1024*1024;
165 push @$vollist, $volid;
166 delete $disk->{format
}; # no longer needed
167 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
170 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
172 my $volid_is_new = 1;
175 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
176 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
181 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
183 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
185 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
187 die "volume $volid does not exists\n" if !$size;
189 $disk->{size
} = $size;
192 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
196 # free allocated images on error
198 syslog
('err', "VM $vmid creating disks failed");
199 foreach my $volid (@$vollist) {
200 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
206 # modify vm config if everything went well
207 foreach my $ds (keys %$res) {
208 $conf->{$ds} = $res->{$ds};
225 my $memoryoptions = {
231 my $hwtypeoptions = {
243 my $generaloptions = {
250 'migrate_downtime' => 1,
251 'migrate_speed' => 1,
263 my $vmpoweroptions = {
272 my $check_vm_modify_config_perm = sub {
273 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
275 return 1 if $authuser eq 'root@pam';
277 foreach my $opt (@$key_list) {
278 # disk checks need to be done somewhere else
279 next if PVE
::QemuServer
::is_valid_drivename
($opt);
280 next if $opt eq 'cdrom';
281 next if $opt =~ m/^unused\d+$/;
283 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
284 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
285 } elsif ($memoryoptions->{$opt}) {
286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
287 } elsif ($hwtypeoptions->{$opt}) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
289 } elsif ($generaloptions->{$opt}) {
290 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
291 # special case for startup since it changes host behaviour
292 if ($opt eq 'startup') {
293 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
295 } elsif ($vmpoweroptions->{$opt}) {
296 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
297 } elsif ($diskoptions->{$opt}) {
298 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
299 } elsif ($opt =~ m/^net\d+$/) {
300 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
302 # catches usb\d+, hostpci\d+, args, lock, etc.
303 # new options will be checked here
304 die "only root can set '$opt' config\n";
311 __PACKAGE__-
>register_method({
315 description
=> "Virtual machine index (per node).",
317 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
321 protected
=> 1, # qemu pid files are only readable by root
323 additionalProperties
=> 0,
325 node
=> get_standard_option
('pve-node'),
329 description
=> "Determine the full status of active VMs.",
339 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
344 my $rpcenv = PVE
::RPCEnvironment
::get
();
345 my $authuser = $rpcenv->get_user();
347 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
350 foreach my $vmid (keys %$vmstatus) {
351 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
353 my $data = $vmstatus->{$vmid};
354 $data->{vmid
} = int($vmid);
363 __PACKAGE__-
>register_method({
367 description
=> "Create or restore a virtual machine.",
369 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
370 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
371 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
372 user
=> 'all', # check inside
377 additionalProperties
=> 0,
378 properties
=> PVE
::QemuServer
::json_config_properties
(
380 node
=> get_standard_option
('pve-node'),
381 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
383 description
=> "The backup file.",
387 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
389 storage
=> get_standard_option
('pve-storage-id', {
390 description
=> "Default storage.",
392 completion
=> \
&PVE
::QemuServer
::complete_storage
,
397 description
=> "Allow to overwrite existing VM.",
398 requires
=> 'archive',
403 description
=> "Assign a unique random ethernet address.",
404 requires
=> 'archive',
408 type
=> 'string', format
=> 'pve-poolid',
409 description
=> "Add the VM to the specified pool.",
419 my $rpcenv = PVE
::RPCEnvironment
::get
();
421 my $authuser = $rpcenv->get_user();
423 my $node = extract_param
($param, 'node');
425 my $vmid = extract_param
($param, 'vmid');
427 my $archive = extract_param
($param, 'archive');
429 my $storage = extract_param
($param, 'storage');
431 my $force = extract_param
($param, 'force');
433 my $unique = extract_param
($param, 'unique');
435 my $pool = extract_param
($param, 'pool');
437 my $filename = PVE
::QemuConfig-
>config_file($vmid);
439 my $storecfg = PVE
::Storage
::config
();
441 PVE
::Cluster
::check_cfs_quorum
();
443 if (defined($pool)) {
444 $rpcenv->check_pool_exist($pool);
447 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
448 if defined($storage);
450 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
452 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
454 } elsif ($archive && $force && (-f
$filename) &&
455 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
456 # OK: user has VM.Backup permissions, and want to restore an existing VM
462 &$resolve_cdrom_alias($param);
464 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
466 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
468 foreach my $opt (keys %$param) {
469 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
470 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
471 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
473 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
474 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
478 PVE
::QemuServer
::add_random_macs
($param);
480 my $keystr = join(' ', keys %$param);
481 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
483 if ($archive eq '-') {
484 die "pipe requires cli environment\n"
485 if $rpcenv->{type
} ne 'cli';
487 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
488 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
492 my $restorefn = sub {
493 my $vmlist = PVE
::Cluster
::get_vmlist
();
494 if ($vmlist->{ids
}->{$vmid}) {
495 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
496 if ($current_node eq $node) {
497 my $conf = PVE
::QemuConfig-
>load_config($vmid);
499 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
501 die "unable to restore vm $vmid - config file already exists\n"
504 die "unable to restore vm $vmid - vm is running\n"
505 if PVE
::QemuServer
::check_running
($vmid);
507 die "unable to restore vm $vmid - vm is a template\n"
508 if PVE
::QemuConfig-
>is_template($conf);
511 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
516 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
519 unique
=> $unique });
521 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
524 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
530 PVE
::Cluster
::check_vmid_unused
($vmid);
540 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
542 # try to be smart about bootdisk
543 my @disks = PVE
::QemuServer
::valid_drive_names
();
545 foreach my $ds (reverse @disks) {
546 next if !$conf->{$ds};
547 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
548 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
552 if (!$conf->{bootdisk
} && $firstdisk) {
553 $conf->{bootdisk
} = $firstdisk;
556 # auto generate uuid if user did not specify smbios1 option
557 if (!$conf->{smbios1
}) {
558 my ($uuid, $uuid_str);
559 UUID
::generate
($uuid);
560 UUID
::unparse
($uuid, $uuid_str);
561 $conf->{smbios1
} = "uuid=$uuid_str";
564 PVE
::QemuConfig-
>write_config($vmid, $conf);
570 foreach my $volid (@$vollist) {
571 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
574 die "create failed - $err";
577 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
580 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
583 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
586 __PACKAGE__-
>register_method({
591 description
=> "Directory index",
596 additionalProperties
=> 0,
598 node
=> get_standard_option
('pve-node'),
599 vmid
=> get_standard_option
('pve-vmid'),
607 subdir
=> { type
=> 'string' },
610 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
616 { subdir
=> 'config' },
617 { subdir
=> 'pending' },
618 { subdir
=> 'status' },
619 { subdir
=> 'unlink' },
620 { subdir
=> 'vncproxy' },
621 { subdir
=> 'migrate' },
622 { subdir
=> 'resize' },
623 { subdir
=> 'move' },
625 { subdir
=> 'rrddata' },
626 { subdir
=> 'monitor' },
627 { subdir
=> 'agent' },
628 { subdir
=> 'snapshot' },
629 { subdir
=> 'spiceproxy' },
630 { subdir
=> 'sendkey' },
631 { subdir
=> 'firewall' },
637 __PACKAGE__-
>register_method ({
638 subclass
=> "PVE::API2::Firewall::VM",
639 path
=> '{vmid}/firewall',
642 __PACKAGE__-
>register_method({
644 path
=> '{vmid}/rrd',
646 protected
=> 1, # fixme: can we avoid that?
648 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
650 description
=> "Read VM RRD statistics (returns PNG)",
652 additionalProperties
=> 0,
654 node
=> get_standard_option
('pve-node'),
655 vmid
=> get_standard_option
('pve-vmid'),
657 description
=> "Specify the time frame you are interested in.",
659 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
662 description
=> "The list of datasources you want to display.",
663 type
=> 'string', format
=> 'pve-configid-list',
666 description
=> "The RRD consolidation function",
668 enum
=> [ 'AVERAGE', 'MAX' ],
676 filename
=> { type
=> 'string' },
682 return PVE
::Cluster
::create_rrd_graph
(
683 "pve2-vm/$param->{vmid}", $param->{timeframe
},
684 $param->{ds
}, $param->{cf
});
688 __PACKAGE__-
>register_method({
690 path
=> '{vmid}/rrddata',
692 protected
=> 1, # fixme: can we avoid that?
694 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
696 description
=> "Read VM RRD statistics",
698 additionalProperties
=> 0,
700 node
=> get_standard_option
('pve-node'),
701 vmid
=> get_standard_option
('pve-vmid'),
703 description
=> "Specify the time frame you are interested in.",
705 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
708 description
=> "The RRD consolidation function",
710 enum
=> [ 'AVERAGE', 'MAX' ],
725 return PVE
::Cluster
::create_rrd_data
(
726 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
730 __PACKAGE__-
>register_method({
732 path
=> '{vmid}/config',
735 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
737 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
740 additionalProperties
=> 0,
742 node
=> get_standard_option
('pve-node'),
743 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
745 description
=> "Get current values (instead of pending values).",
757 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
764 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
766 delete $conf->{snapshots
};
768 if (!$param->{current
}) {
769 foreach my $opt (keys %{$conf->{pending
}}) {
770 next if $opt eq 'delete';
771 my $value = $conf->{pending
}->{$opt};
772 next if ref($value); # just to be sure
773 $conf->{$opt} = $value;
775 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
776 foreach my $opt (keys %$pending_delete_hash) {
777 delete $conf->{$opt} if $conf->{$opt};
781 delete $conf->{pending
};
786 __PACKAGE__-
>register_method({
787 name
=> 'vm_pending',
788 path
=> '{vmid}/pending',
791 description
=> "Get virtual machine configuration, including pending changes.",
793 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
796 additionalProperties
=> 0,
798 node
=> get_standard_option
('pve-node'),
799 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
808 description
=> "Configuration option name.",
812 description
=> "Current value.",
817 description
=> "Pending value.",
822 description
=> "Indicates a pending delete request if present and not 0. " .
823 "The value 2 indicates a force-delete request.",
835 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
837 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
841 foreach my $opt (keys %$conf) {
842 next if ref($conf->{$opt});
843 my $item = { key
=> $opt };
844 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
845 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
846 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
850 foreach my $opt (keys %{$conf->{pending
}}) {
851 next if $opt eq 'delete';
852 next if ref($conf->{pending
}->{$opt}); # just to be sure
853 next if defined($conf->{$opt});
854 my $item = { key
=> $opt };
855 $item->{pending
} = $conf->{pending
}->{$opt};
859 while (my ($opt, $force) = each %$pending_delete_hash) {
860 next if $conf->{pending
}->{$opt}; # just to be sure
861 next if $conf->{$opt};
862 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
869 # POST/PUT {vmid}/config implementation
871 # The original API used PUT (idempotent) an we assumed that all operations
872 # are fast. But it turned out that almost any configuration change can
873 # involve hot-plug actions, or disk alloc/free. Such actions can take long
874 # time to complete and have side effects (not idempotent).
876 # The new implementation uses POST and forks a worker process. We added
877 # a new option 'background_delay'. If specified we wait up to
878 # 'background_delay' second for the worker task to complete. It returns null
879 # if the task is finished within that time, else we return the UPID.
881 my $update_vm_api = sub {
882 my ($param, $sync) = @_;
884 my $rpcenv = PVE
::RPCEnvironment
::get
();
886 my $authuser = $rpcenv->get_user();
888 my $node = extract_param
($param, 'node');
890 my $vmid = extract_param
($param, 'vmid');
892 my $digest = extract_param
($param, 'digest');
894 my $background_delay = extract_param
($param, 'background_delay');
896 my @paramarr = (); # used for log message
897 foreach my $key (keys %$param) {
898 push @paramarr, "-$key", $param->{$key};
901 my $skiplock = extract_param
($param, 'skiplock');
902 raise_param_exc
({ skiplock
=> "Only root may use this option." })
903 if $skiplock && $authuser ne 'root@pam';
905 my $delete_str = extract_param
($param, 'delete');
907 my $revert_str = extract_param
($param, 'revert');
909 my $force = extract_param
($param, 'force');
911 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
913 my $storecfg = PVE
::Storage
::config
();
915 my $defaults = PVE
::QemuServer
::load_defaults
();
917 &$resolve_cdrom_alias($param);
919 # now try to verify all parameters
922 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
923 if (!PVE
::QemuServer
::option_exists
($opt)) {
924 raise_param_exc
({ revert
=> "unknown option '$opt'" });
927 raise_param_exc
({ delete => "you can't use '-$opt' and " .
928 "-revert $opt' at the same time" })
929 if defined($param->{$opt});
935 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
936 $opt = 'ide2' if $opt eq 'cdrom';
938 raise_param_exc
({ delete => "you can't use '-$opt' and " .
939 "-delete $opt' at the same time" })
940 if defined($param->{$opt});
942 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
943 "-revert $opt' at the same time" })
946 if (!PVE
::QemuServer
::option_exists
($opt)) {
947 raise_param_exc
({ delete => "unknown option '$opt'" });
953 foreach my $opt (keys %$param) {
954 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
956 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
957 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
958 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
959 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
960 } elsif ($opt =~ m/^net(\d+)$/) {
962 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
963 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
967 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
969 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
971 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
975 my $conf = PVE
::QemuConfig-
>load_config($vmid);
977 die "checksum missmatch (file change by other user?)\n"
978 if $digest && $digest ne $conf->{digest
};
980 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
982 foreach my $opt (keys %$revert) {
983 if (defined($conf->{$opt})) {
984 $param->{$opt} = $conf->{$opt};
985 } elsif (defined($conf->{pending
}->{$opt})) {
990 if ($param->{memory
} || defined($param->{balloon
})) {
991 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
992 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
994 die "balloon value too large (must be smaller than assigned memory)\n"
995 if $balloon && $balloon > $maxmem;
998 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1002 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1004 # write updates to pending section
1006 my $modified = {}; # record what $option we modify
1008 foreach my $opt (@delete) {
1009 $modified->{$opt} = 1;
1010 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1011 if (!defined($conf->{$opt})) {
1012 warn "cannot delete '$opt' - not set in current configuration!\n";
1013 $modified->{$opt} = 0;
1017 if ($opt =~ m/^unused/) {
1018 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1019 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1020 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1021 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1022 delete $conf->{$opt};
1023 PVE
::QemuConfig-
>write_config($vmid, $conf);
1025 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1026 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1027 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1028 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1029 if defined($conf->{pending
}->{$opt});
1030 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1031 PVE
::QemuConfig-
>write_config($vmid, $conf);
1032 } elsif ($opt eq "replica" || $opt eq "replica_target") {
1033 delete $conf->{$opt};
1034 delete $conf->{replica
} if $opt eq "replica_target";
1036 PVE
::QemuConfig-
>write_config($vmid, $conf);
1037 PVE
::ReplicationTools
::job_remove
($vmid);
1038 } elsif ($opt eq "replica_interval" || $opt eq "replica_rate_limit") {
1039 delete $conf->{$opt};
1040 PVE
::QemuConfig-
>write_config($vmid, $conf);
1041 PVE
::ReplicationTools
::update_conf
($vmid, $opt, $param->{$opt});
1043 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1044 PVE
::QemuConfig-
>write_config($vmid, $conf);
1048 foreach my $opt (keys %$param) { # add/change
1049 $modified->{$opt} = 1;
1050 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1051 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1053 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1054 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1055 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1056 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1058 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1060 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1061 if defined($conf->{pending
}->{$opt});
1063 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1064 } elsif ($opt eq "replica") {
1065 die "Not all volumes are syncable, please check your config\n"
1066 if !PVE
::ReplicationTools
::check_guest_volumes_syncable
($conf, 'qemu');
1067 die "replica_target is required\n"
1068 if !$conf->{replica_target
} && !$param->{replica_target
};
1069 $conf->{$opt} = $param->{$opt};
1070 } elsif ($opt eq "replica_interval" || $opt eq "replica_rate_limit") {
1071 $conf->{$opt} = $param->{$opt};
1072 PVE
::ReplicationTools
::update_conf
($vmid, $opt, $param->{$opt});
1073 } elsif ($opt eq "replica_target" ) {
1074 die "Node: $param->{$opt} does not exists in Cluster.\n"
1075 if !PVE
::Cluster
::check_node_exists
($param->{$opt});
1076 PVE
::ReplicationTools
::update_conf
($vmid, $opt, $param->{$opt})
1077 if defined($conf->{$opt});
1078 $conf->{$opt} = $param->{$opt};
1080 $conf->{pending
}->{$opt} = $param->{$opt};
1082 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1083 PVE
::QemuConfig-
>write_config($vmid, $conf);
1086 if (defined($param->{replica
})) {
1087 if ($param->{replica
}) {
1088 PVE
::ReplicationTools
::job_enable
($vmid);
1090 PVE
::ReplicationTools
::job_disable
($vmid);
1094 # remove pending changes when nothing changed
1095 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1096 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1097 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1099 return if !scalar(keys %{$conf->{pending
}});
1101 my $running = PVE
::QemuServer
::check_running
($vmid);
1103 # apply pending changes
1105 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1109 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1110 raise_param_exc
($errors) if scalar(keys %$errors);
1112 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1122 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1124 if ($background_delay) {
1126 # Note: It would be better to do that in the Event based HTTPServer
1127 # to avoid blocking call to sleep.
1129 my $end_time = time() + $background_delay;
1131 my $task = PVE
::Tools
::upid_decode
($upid);
1134 while (time() < $end_time) {
1135 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1137 sleep(1); # this gets interrupted when child process ends
1141 my $status = PVE
::Tools
::upid_read_status
($upid);
1142 return undef if $status eq 'OK';
1151 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1154 my $vm_config_perm_list = [
1159 'VM.Config.Network',
1161 'VM.Config.Options',
1164 __PACKAGE__-
>register_method({
1165 name
=> 'update_vm_async',
1166 path
=> '{vmid}/config',
1170 description
=> "Set virtual machine options (asynchrounous API).",
1172 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1175 additionalProperties
=> 0,
1176 properties
=> PVE
::QemuServer
::json_config_properties
(
1178 node
=> get_standard_option
('pve-node'),
1179 vmid
=> get_standard_option
('pve-vmid'),
1180 skiplock
=> get_standard_option
('skiplock'),
1182 type
=> 'string', format
=> 'pve-configid-list',
1183 description
=> "A list of settings you want to delete.",
1187 type
=> 'string', format
=> 'pve-configid-list',
1188 description
=> "Revert a pending change.",
1193 description
=> $opt_force_description,
1195 requires
=> 'delete',
1199 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1203 background_delay
=> {
1205 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1216 code
=> $update_vm_api,
1219 __PACKAGE__-
>register_method({
1220 name
=> 'update_vm',
1221 path
=> '{vmid}/config',
1225 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1227 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1230 additionalProperties
=> 0,
1231 properties
=> PVE
::QemuServer
::json_config_properties
(
1233 node
=> get_standard_option
('pve-node'),
1234 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1235 skiplock
=> get_standard_option
('skiplock'),
1237 type
=> 'string', format
=> 'pve-configid-list',
1238 description
=> "A list of settings you want to delete.",
1242 type
=> 'string', format
=> 'pve-configid-list',
1243 description
=> "Revert a pending change.",
1248 description
=> $opt_force_description,
1250 requires
=> 'delete',
1254 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1260 returns
=> { type
=> 'null' },
1263 &$update_vm_api($param, 1);
1269 __PACKAGE__-
>register_method({
1270 name
=> 'destroy_vm',
1275 description
=> "Destroy the vm (also delete all used/owned volumes).",
1277 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1280 additionalProperties
=> 0,
1282 node
=> get_standard_option
('pve-node'),
1283 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1284 skiplock
=> get_standard_option
('skiplock'),
1293 my $rpcenv = PVE
::RPCEnvironment
::get
();
1295 my $authuser = $rpcenv->get_user();
1297 my $vmid = $param->{vmid
};
1299 my $skiplock = $param->{skiplock
};
1300 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1301 if $skiplock && $authuser ne 'root@pam';
1304 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1306 my $storecfg = PVE
::Storage
::config
();
1308 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1310 die "unable to remove VM $vmid - used in HA resources\n"
1311 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1313 # early tests (repeat after locking)
1314 die "VM $vmid is running - destroy failed\n"
1315 if PVE
::QemuServer
::check_running
($vmid);
1320 syslog
('info', "destroy VM $vmid: $upid\n");
1322 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1324 PVE
::AccessControl
::remove_vm_access
($vmid);
1326 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1329 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1332 __PACKAGE__-
>register_method({
1334 path
=> '{vmid}/unlink',
1338 description
=> "Unlink/delete disk images.",
1340 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1343 additionalProperties
=> 0,
1345 node
=> get_standard_option
('pve-node'),
1346 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1348 type
=> 'string', format
=> 'pve-configid-list',
1349 description
=> "A list of disk IDs you want to delete.",
1353 description
=> $opt_force_description,
1358 returns
=> { type
=> 'null'},
1362 $param->{delete} = extract_param
($param, 'idlist');
1364 __PACKAGE__-
>update_vm($param);
1371 __PACKAGE__-
>register_method({
1373 path
=> '{vmid}/vncproxy',
1377 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1379 description
=> "Creates a TCP VNC proxy connections.",
1381 additionalProperties
=> 0,
1383 node
=> get_standard_option
('pve-node'),
1384 vmid
=> get_standard_option
('pve-vmid'),
1388 description
=> "starts websockify instead of vncproxy",
1393 additionalProperties
=> 0,
1395 user
=> { type
=> 'string' },
1396 ticket
=> { type
=> 'string' },
1397 cert
=> { type
=> 'string' },
1398 port
=> { type
=> 'integer' },
1399 upid
=> { type
=> 'string' },
1405 my $rpcenv = PVE
::RPCEnvironment
::get
();
1407 my $authuser = $rpcenv->get_user();
1409 my $vmid = $param->{vmid
};
1410 my $node = $param->{node
};
1411 my $websocket = $param->{websocket
};
1413 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1415 my $authpath = "/vms/$vmid";
1417 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1419 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1422 my ($remip, $family);
1425 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1426 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1427 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1428 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1430 $family = PVE
::Tools
::get_host_address_family
($node);
1433 my $port = PVE
::Tools
::next_vnc_port
($family);
1440 syslog
('info', "starting vnc proxy $upid\n");
1444 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1446 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1448 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1449 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1450 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1451 '-timeout', $timeout, '-authpath', $authpath,
1452 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1453 PVE
::Tools
::run_command
($cmd);
1456 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1458 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1460 my $sock = IO
::Socket
::IP-
>new(
1464 GetAddrInfoFlags
=> 0,
1465 ) or die "failed to create socket: $!\n";
1466 # Inside the worker we shouldn't have any previous alarms
1467 # running anyway...:
1469 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1471 accept(my $cli, $sock) or die "connection failed: $!\n";
1474 if (PVE
::Tools
::run_command
($cmd,
1475 output
=> '>&'.fileno($cli),
1476 input
=> '<&'.fileno($cli),
1479 die "Failed to run vncproxy.\n";
1486 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1488 PVE
::Tools
::wait_for_vnc_port
($port);
1499 __PACKAGE__-
>register_method({
1500 name
=> 'vncwebsocket',
1501 path
=> '{vmid}/vncwebsocket',
1504 description
=> "You also need to pass a valid ticket (vncticket).",
1505 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1507 description
=> "Opens a weksocket for VNC traffic.",
1509 additionalProperties
=> 0,
1511 node
=> get_standard_option
('pve-node'),
1512 vmid
=> get_standard_option
('pve-vmid'),
1514 description
=> "Ticket from previous call to vncproxy.",
1519 description
=> "Port number returned by previous vncproxy call.",
1529 port
=> { type
=> 'string' },
1535 my $rpcenv = PVE
::RPCEnvironment
::get
();
1537 my $authuser = $rpcenv->get_user();
1539 my $vmid = $param->{vmid
};
1540 my $node = $param->{node
};
1542 my $authpath = "/vms/$vmid";
1544 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1546 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1548 # Note: VNC ports are acessible from outside, so we do not gain any
1549 # security if we verify that $param->{port} belongs to VM $vmid. This
1550 # check is done by verifying the VNC ticket (inside VNC protocol).
1552 my $port = $param->{port
};
1554 return { port
=> $port };
1557 __PACKAGE__-
>register_method({
1558 name
=> 'spiceproxy',
1559 path
=> '{vmid}/spiceproxy',
1564 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1566 description
=> "Returns a SPICE configuration to connect to the VM.",
1568 additionalProperties
=> 0,
1570 node
=> get_standard_option
('pve-node'),
1571 vmid
=> get_standard_option
('pve-vmid'),
1572 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1575 returns
=> get_standard_option
('remote-viewer-config'),
1579 my $rpcenv = PVE
::RPCEnvironment
::get
();
1581 my $authuser = $rpcenv->get_user();
1583 my $vmid = $param->{vmid
};
1584 my $node = $param->{node
};
1585 my $proxy = $param->{proxy
};
1587 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1588 my $title = "VM $vmid";
1589 $title .= " - ". $conf->{name
} if $conf->{name
};
1591 my $port = PVE
::QemuServer
::spice_port
($vmid);
1593 my ($ticket, undef, $remote_viewer_config) =
1594 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1596 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1597 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1599 return $remote_viewer_config;
1602 __PACKAGE__-
>register_method({
1604 path
=> '{vmid}/status',
1607 description
=> "Directory index",
1612 additionalProperties
=> 0,
1614 node
=> get_standard_option
('pve-node'),
1615 vmid
=> get_standard_option
('pve-vmid'),
1623 subdir
=> { type
=> 'string' },
1626 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1632 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1635 { subdir
=> 'current' },
1636 { subdir
=> 'start' },
1637 { subdir
=> 'stop' },
1643 __PACKAGE__-
>register_method({
1644 name
=> 'vm_status',
1645 path
=> '{vmid}/status/current',
1648 protected
=> 1, # qemu pid files are only readable by root
1649 description
=> "Get virtual machine status.",
1651 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1654 additionalProperties
=> 0,
1656 node
=> get_standard_option
('pve-node'),
1657 vmid
=> get_standard_option
('pve-vmid'),
1660 returns
=> { type
=> 'object' },
1665 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1667 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1668 my $status = $vmstatus->{$param->{vmid
}};
1670 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1672 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1677 __PACKAGE__-
>register_method({
1679 path
=> '{vmid}/status/start',
1683 description
=> "Start virtual machine.",
1685 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1688 additionalProperties
=> 0,
1690 node
=> get_standard_option
('pve-node'),
1691 vmid
=> get_standard_option
('pve-vmid',
1692 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1693 skiplock
=> get_standard_option
('skiplock'),
1694 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1695 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1698 enum
=> ['secure', 'insecure'],
1699 description
=> "Migration traffic is encrypted using an SSH " .
1700 "tunnel by default. On secure, completely private networks " .
1701 "this can be disabled to increase performance.",
1704 migration_network
=> {
1705 type
=> 'string', format
=> 'CIDR',
1706 description
=> "CIDR of the (sub) network that is used for migration.",
1709 machine
=> get_standard_option
('pve-qm-machine'),
1711 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1723 my $rpcenv = PVE
::RPCEnvironment
::get
();
1725 my $authuser = $rpcenv->get_user();
1727 my $node = extract_param
($param, 'node');
1729 my $vmid = extract_param
($param, 'vmid');
1731 my $machine = extract_param
($param, 'machine');
1733 my $stateuri = extract_param
($param, 'stateuri');
1734 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1735 if $stateuri && $authuser ne 'root@pam';
1737 my $skiplock = extract_param
($param, 'skiplock');
1738 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1739 if $skiplock && $authuser ne 'root@pam';
1741 my $migratedfrom = extract_param
($param, 'migratedfrom');
1742 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1743 if $migratedfrom && $authuser ne 'root@pam';
1745 my $migration_type = extract_param
($param, 'migration_type');
1746 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1747 if $migration_type && $authuser ne 'root@pam';
1749 my $migration_network = extract_param
($param, 'migration_network');
1750 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1751 if $migration_network && $authuser ne 'root@pam';
1753 my $targetstorage = extract_param
($param, 'targetstorage');
1754 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1755 if $targetstorage && $authuser ne 'root@pam';
1757 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1758 if $targetstorage && !$migratedfrom;
1760 # read spice ticket from STDIN
1762 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1763 if (defined(my $line = <>)) {
1765 $spice_ticket = $line;
1769 PVE
::Cluster
::check_cfs_quorum
();
1771 my $storecfg = PVE
::Storage
::config
();
1773 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1774 $rpcenv->{type
} ne 'ha') {
1779 my $service = "vm:$vmid";
1781 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1783 print "Executing HA start for VM $vmid\n";
1785 PVE
::Tools
::run_command
($cmd);
1790 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1797 syslog
('info', "start VM $vmid: $upid\n");
1799 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1800 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1805 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1809 __PACKAGE__-
>register_method({
1811 path
=> '{vmid}/status/stop',
1815 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1816 "is akin to pulling the power plug of a running computer and may damage the VM data",
1818 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1821 additionalProperties
=> 0,
1823 node
=> get_standard_option
('pve-node'),
1824 vmid
=> get_standard_option
('pve-vmid',
1825 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1826 skiplock
=> get_standard_option
('skiplock'),
1827 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1829 description
=> "Wait maximal timeout seconds.",
1835 description
=> "Do not deactivate storage volumes.",
1848 my $rpcenv = PVE
::RPCEnvironment
::get
();
1850 my $authuser = $rpcenv->get_user();
1852 my $node = extract_param
($param, 'node');
1854 my $vmid = extract_param
($param, 'vmid');
1856 my $skiplock = extract_param
($param, 'skiplock');
1857 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1858 if $skiplock && $authuser ne 'root@pam';
1860 my $keepActive = extract_param
($param, 'keepActive');
1861 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1862 if $keepActive && $authuser ne 'root@pam';
1864 my $migratedfrom = extract_param
($param, 'migratedfrom');
1865 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1866 if $migratedfrom && $authuser ne 'root@pam';
1869 my $storecfg = PVE
::Storage
::config
();
1871 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1876 my $service = "vm:$vmid";
1878 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1880 print "Executing HA stop for VM $vmid\n";
1882 PVE
::Tools
::run_command
($cmd);
1887 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1893 syslog
('info', "stop VM $vmid: $upid\n");
1895 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1896 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1901 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1905 __PACKAGE__-
>register_method({
1907 path
=> '{vmid}/status/reset',
1911 description
=> "Reset virtual machine.",
1913 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1916 additionalProperties
=> 0,
1918 node
=> get_standard_option
('pve-node'),
1919 vmid
=> get_standard_option
('pve-vmid',
1920 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1921 skiplock
=> get_standard_option
('skiplock'),
1930 my $rpcenv = PVE
::RPCEnvironment
::get
();
1932 my $authuser = $rpcenv->get_user();
1934 my $node = extract_param
($param, 'node');
1936 my $vmid = extract_param
($param, 'vmid');
1938 my $skiplock = extract_param
($param, 'skiplock');
1939 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1940 if $skiplock && $authuser ne 'root@pam';
1942 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1947 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1952 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1955 __PACKAGE__-
>register_method({
1956 name
=> 'vm_shutdown',
1957 path
=> '{vmid}/status/shutdown',
1961 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1962 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1964 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1967 additionalProperties
=> 0,
1969 node
=> get_standard_option
('pve-node'),
1970 vmid
=> get_standard_option
('pve-vmid',
1971 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1972 skiplock
=> get_standard_option
('skiplock'),
1974 description
=> "Wait maximal timeout seconds.",
1980 description
=> "Make sure the VM stops.",
1986 description
=> "Do not deactivate storage volumes.",
1999 my $rpcenv = PVE
::RPCEnvironment
::get
();
2001 my $authuser = $rpcenv->get_user();
2003 my $node = extract_param
($param, 'node');
2005 my $vmid = extract_param
($param, 'vmid');
2007 my $skiplock = extract_param
($param, 'skiplock');
2008 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2009 if $skiplock && $authuser ne 'root@pam';
2011 my $keepActive = extract_param
($param, 'keepActive');
2012 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2013 if $keepActive && $authuser ne 'root@pam';
2015 my $storecfg = PVE
::Storage
::config
();
2019 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2020 # otherwise, we will infer a shutdown command, but run into the timeout,
2021 # then when the vm is resumed, it will instantly shutdown
2023 # checking the qmp status here to get feedback to the gui/cli/api
2024 # and the status query should not take too long
2027 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2031 if (!$err && $qmpstatus->{status
} eq "paused") {
2032 if ($param->{forceStop
}) {
2033 warn "VM is paused - stop instead of shutdown\n";
2036 die "VM is paused - cannot shutdown\n";
2040 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2041 ($rpcenv->{type
} ne 'ha')) {
2046 my $service = "vm:$vmid";
2048 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2050 print "Executing HA stop for VM $vmid\n";
2052 PVE
::Tools
::run_command
($cmd);
2057 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2064 syslog
('info', "shutdown VM $vmid: $upid\n");
2066 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2067 $shutdown, $param->{forceStop
}, $keepActive);
2072 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2076 __PACKAGE__-
>register_method({
2077 name
=> 'vm_suspend',
2078 path
=> '{vmid}/status/suspend',
2082 description
=> "Suspend virtual machine.",
2084 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2087 additionalProperties
=> 0,
2089 node
=> get_standard_option
('pve-node'),
2090 vmid
=> get_standard_option
('pve-vmid',
2091 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2092 skiplock
=> get_standard_option
('skiplock'),
2101 my $rpcenv = PVE
::RPCEnvironment
::get
();
2103 my $authuser = $rpcenv->get_user();
2105 my $node = extract_param
($param, 'node');
2107 my $vmid = extract_param
($param, 'vmid');
2109 my $skiplock = extract_param
($param, 'skiplock');
2110 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2111 if $skiplock && $authuser ne 'root@pam';
2113 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2118 syslog
('info', "suspend VM $vmid: $upid\n");
2120 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2125 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2128 __PACKAGE__-
>register_method({
2129 name
=> 'vm_resume',
2130 path
=> '{vmid}/status/resume',
2134 description
=> "Resume virtual machine.",
2136 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2139 additionalProperties
=> 0,
2141 node
=> get_standard_option
('pve-node'),
2142 vmid
=> get_standard_option
('pve-vmid',
2143 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2144 skiplock
=> get_standard_option
('skiplock'),
2145 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2155 my $rpcenv = PVE
::RPCEnvironment
::get
();
2157 my $authuser = $rpcenv->get_user();
2159 my $node = extract_param
($param, 'node');
2161 my $vmid = extract_param
($param, 'vmid');
2163 my $skiplock = extract_param
($param, 'skiplock');
2164 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2165 if $skiplock && $authuser ne 'root@pam';
2167 my $nocheck = extract_param
($param, 'nocheck');
2169 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2174 syslog
('info', "resume VM $vmid: $upid\n");
2176 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2181 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2184 __PACKAGE__-
>register_method({
2185 name
=> 'vm_sendkey',
2186 path
=> '{vmid}/sendkey',
2190 description
=> "Send key event to virtual machine.",
2192 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2195 additionalProperties
=> 0,
2197 node
=> get_standard_option
('pve-node'),
2198 vmid
=> get_standard_option
('pve-vmid',
2199 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2200 skiplock
=> get_standard_option
('skiplock'),
2202 description
=> "The key (qemu monitor encoding).",
2207 returns
=> { type
=> 'null'},
2211 my $rpcenv = PVE
::RPCEnvironment
::get
();
2213 my $authuser = $rpcenv->get_user();
2215 my $node = extract_param
($param, 'node');
2217 my $vmid = extract_param
($param, 'vmid');
2219 my $skiplock = extract_param
($param, 'skiplock');
2220 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2221 if $skiplock && $authuser ne 'root@pam';
2223 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2228 __PACKAGE__-
>register_method({
2229 name
=> 'vm_feature',
2230 path
=> '{vmid}/feature',
2234 description
=> "Check if feature for virtual machine is available.",
2236 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2239 additionalProperties
=> 0,
2241 node
=> get_standard_option
('pve-node'),
2242 vmid
=> get_standard_option
('pve-vmid'),
2244 description
=> "Feature to check.",
2246 enum
=> [ 'snapshot', 'clone', 'copy' ],
2248 snapname
=> get_standard_option
('pve-snapshot-name', {
2256 hasFeature
=> { type
=> 'boolean' },
2259 items
=> { type
=> 'string' },
2266 my $node = extract_param
($param, 'node');
2268 my $vmid = extract_param
($param, 'vmid');
2270 my $snapname = extract_param
($param, 'snapname');
2272 my $feature = extract_param
($param, 'feature');
2274 my $running = PVE
::QemuServer
::check_running
($vmid);
2276 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2279 my $snap = $conf->{snapshots
}->{$snapname};
2280 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2283 my $storecfg = PVE
::Storage
::config
();
2285 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2286 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2289 hasFeature
=> $hasFeature,
2290 nodes
=> [ keys %$nodelist ],
2294 __PACKAGE__-
>register_method({
2296 path
=> '{vmid}/clone',
2300 description
=> "Create a copy of virtual machine/template.",
2302 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2303 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2304 "'Datastore.AllocateSpace' on any used storage.",
2307 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2309 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2310 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2315 additionalProperties
=> 0,
2317 node
=> get_standard_option
('pve-node'),
2318 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2319 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2322 type
=> 'string', format
=> 'dns-name',
2323 description
=> "Set a name for the new VM.",
2328 description
=> "Description for the new VM.",
2332 type
=> 'string', format
=> 'pve-poolid',
2333 description
=> "Add the new VM to the specified pool.",
2335 snapname
=> get_standard_option
('pve-snapshot-name', {
2338 storage
=> get_standard_option
('pve-storage-id', {
2339 description
=> "Target storage for full clone.",
2344 description
=> "Target format for file storage.",
2348 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2353 description
=> "Create a full copy of all disk. This is always done when " .
2354 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2357 target
=> get_standard_option
('pve-node', {
2358 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2369 my $rpcenv = PVE
::RPCEnvironment
::get
();
2371 my $authuser = $rpcenv->get_user();
2373 my $node = extract_param
($param, 'node');
2375 my $vmid = extract_param
($param, 'vmid');
2377 my $newid = extract_param
($param, 'newid');
2379 my $pool = extract_param
($param, 'pool');
2381 if (defined($pool)) {
2382 $rpcenv->check_pool_exist($pool);
2385 my $snapname = extract_param
($param, 'snapname');
2387 my $storage = extract_param
($param, 'storage');
2389 my $format = extract_param
($param, 'format');
2391 my $target = extract_param
($param, 'target');
2393 my $localnode = PVE
::INotify
::nodename
();
2395 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2397 PVE
::Cluster
::check_node_exists
($target) if $target;
2399 my $storecfg = PVE
::Storage
::config
();
2402 # check if storage is enabled on local node
2403 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2405 # check if storage is available on target node
2406 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2407 # clone only works if target storage is shared
2408 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2409 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2413 PVE
::Cluster
::check_cfs_quorum
();
2415 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2417 # exclusive lock if VM is running - else shared lock is enough;
2418 my $shared_lock = $running ?
0 : 1;
2422 # do all tests after lock
2423 # we also try to do all tests before we fork the worker
2425 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2427 PVE
::QemuConfig-
>check_lock($conf);
2429 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2431 die "unexpected state change\n" if $verify_running != $running;
2433 die "snapshot '$snapname' does not exist\n"
2434 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2436 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2438 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2440 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2442 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2444 die "unable to create VM $newid: config file already exists\n"
2447 my $newconf = { lock => 'clone' };
2452 foreach my $opt (keys %$oldconf) {
2453 my $value = $oldconf->{$opt};
2455 # do not copy snapshot related info
2456 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2457 $opt eq 'vmstate' || $opt eq 'snapstate';
2459 # no need to copy unused images, because VMID(owner) changes anyways
2460 next if $opt =~ m/^unused\d+$/;
2462 # always change MAC! address
2463 if ($opt =~ m/^net(\d+)$/) {
2464 my $net = PVE
::QemuServer
::parse_net
($value);
2465 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2466 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2467 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2468 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2469 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2470 die "unable to parse drive options for '$opt'\n" if !$drive;
2471 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2472 $newconf->{$opt} = $value; # simply copy configuration
2474 if ($param->{full
}) {
2475 die "Full clone feature is not available"
2476 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2477 $fullclone->{$opt} = 1;
2479 # not full means clone instead of copy
2480 die "Linked clone feature is not available"
2481 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2483 $drives->{$opt} = $drive;
2484 push @$vollist, $drive->{file
};
2487 # copy everything else
2488 $newconf->{$opt} = $value;
2492 # auto generate a new uuid
2493 my ($uuid, $uuid_str);
2494 UUID
::generate
($uuid);
2495 UUID
::unparse
($uuid, $uuid_str);
2496 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2497 $smbios1->{uuid
} = $uuid_str;
2498 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2500 delete $newconf->{template
};
2502 if ($param->{name
}) {
2503 $newconf->{name
} = $param->{name
};
2505 if ($oldconf->{name
}) {
2506 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2508 $newconf->{name
} = "Copy-of-VM-$vmid";
2512 if ($param->{description
}) {
2513 $newconf->{description
} = $param->{description
};
2516 # create empty/temp config - this fails if VM already exists on other node
2517 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2522 my $newvollist = [];
2526 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2528 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2530 my $total_jobs = scalar(keys %{$drives});
2533 foreach my $opt (keys %$drives) {
2534 my $drive = $drives->{$opt};
2535 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2537 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2538 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2539 $jobs, $skipcomplete, $oldconf->{agent
});
2541 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2543 PVE
::QemuConfig-
>write_config($newid, $newconf);
2547 delete $newconf->{lock};
2548 PVE
::QemuConfig-
>write_config($newid, $newconf);
2551 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2552 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2553 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2555 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2556 die "Failed to move config to node '$target' - rename failed: $!\n"
2557 if !rename($conffile, $newconffile);
2560 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2565 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2567 sleep 1; # some storage like rbd need to wait before release volume - really?
2569 foreach my $volid (@$newvollist) {
2570 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2573 die "clone failed: $err";
2579 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2581 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2584 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2585 # Aquire exclusive lock lock for $newid
2586 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2591 __PACKAGE__-
>register_method({
2592 name
=> 'move_vm_disk',
2593 path
=> '{vmid}/move_disk',
2597 description
=> "Move volume to different storage.",
2599 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2601 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2602 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2606 additionalProperties
=> 0,
2608 node
=> get_standard_option
('pve-node'),
2609 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2612 description
=> "The disk you want to move.",
2613 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2615 storage
=> get_standard_option
('pve-storage-id', {
2616 description
=> "Target storage.",
2617 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2621 description
=> "Target Format.",
2622 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2627 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2633 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2641 description
=> "the task ID.",
2646 my $rpcenv = PVE
::RPCEnvironment
::get
();
2648 my $authuser = $rpcenv->get_user();
2650 my $node = extract_param
($param, 'node');
2652 my $vmid = extract_param
($param, 'vmid');
2654 my $digest = extract_param
($param, 'digest');
2656 my $disk = extract_param
($param, 'disk');
2658 my $storeid = extract_param
($param, 'storage');
2660 my $format = extract_param
($param, 'format');
2662 my $storecfg = PVE
::Storage
::config
();
2664 my $updatefn = sub {
2666 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2668 PVE
::QemuConfig-
>check_lock($conf);
2670 die "checksum missmatch (file change by other user?)\n"
2671 if $digest && $digest ne $conf->{digest
};
2673 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2675 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2677 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2679 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2682 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2683 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2687 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2688 (!$format || !$oldfmt || $oldfmt eq $format);
2690 # this only checks snapshots because $disk is passed!
2691 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2692 die "you can't move a disk with snapshots and delete the source\n"
2693 if $snapshotted && $param->{delete};
2695 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2697 my $running = PVE
::QemuServer
::check_running
($vmid);
2699 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2703 my $newvollist = [];
2706 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2708 warn "moving disk with snapshots, snapshots will not be moved!\n"
2711 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2712 $vmid, $storeid, $format, 1, $newvollist);
2714 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2716 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2718 # convert moved disk to base if part of template
2719 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2720 if PVE
::QemuConfig-
>is_template($conf);
2722 PVE
::QemuConfig-
>write_config($vmid, $conf);
2725 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2726 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2733 foreach my $volid (@$newvollist) {
2734 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2737 die "storage migration failed: $err";
2740 if ($param->{delete}) {
2742 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2743 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2749 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2752 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2755 __PACKAGE__-
>register_method({
2756 name
=> 'migrate_vm',
2757 path
=> '{vmid}/migrate',
2761 description
=> "Migrate virtual machine. Creates a new migration task.",
2763 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2766 additionalProperties
=> 0,
2768 node
=> get_standard_option
('pve-node'),
2769 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2770 target
=> get_standard_option
('pve-node', {
2771 description
=> "Target node.",
2772 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2776 description
=> "Use online/live migration.",
2781 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2786 enum
=> ['secure', 'insecure'],
2787 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2790 migration_network
=> {
2791 type
=> 'string', format
=> 'CIDR',
2792 description
=> "CIDR of the (sub) network that is used for migration.",
2795 "with-local-disks" => {
2797 description
=> "Enable live storage migration for local disk",
2800 targetstorage
=> get_standard_option
('pve-storage-id', {
2801 description
=> "Default target storage.",
2803 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2809 description
=> "the task ID.",
2814 my $rpcenv = PVE
::RPCEnvironment
::get
();
2816 my $authuser = $rpcenv->get_user();
2818 my $target = extract_param
($param, 'target');
2820 my $localnode = PVE
::INotify
::nodename
();
2821 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2823 PVE
::Cluster
::check_cfs_quorum
();
2825 PVE
::Cluster
::check_node_exists
($target);
2827 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2829 my $vmid = extract_param
($param, 'vmid');
2831 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2832 if !$param->{online
} && $param->{targetstorage
};
2834 raise_param_exc
({ force
=> "Only root may use this option." })
2835 if $param->{force
} && $authuser ne 'root@pam';
2837 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2838 if $param->{migration_type
} && $authuser ne 'root@pam';
2840 # allow root only until better network permissions are available
2841 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2842 if $param->{migration_network
} && $authuser ne 'root@pam';
2845 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2847 # try to detect errors early
2849 PVE
::QemuConfig-
>check_lock($conf);
2851 if (PVE
::QemuServer
::check_running
($vmid)) {
2852 die "cant migrate running VM without --online\n"
2853 if !$param->{online
};
2856 my $storecfg = PVE
::Storage
::config
();
2858 if( $param->{targetstorage
}) {
2859 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2861 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2864 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2869 my $service = "vm:$vmid";
2871 my $cmd = ['ha-manager', 'migrate', $service, $target];
2873 print "Executing HA migrate for VM $vmid to node $target\n";
2875 PVE
::Tools
::run_command
($cmd);
2880 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2887 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2890 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2895 __PACKAGE__-
>register_method({
2897 path
=> '{vmid}/monitor',
2901 description
=> "Execute Qemu monitor commands.",
2903 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2904 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2907 additionalProperties
=> 0,
2909 node
=> get_standard_option
('pve-node'),
2910 vmid
=> get_standard_option
('pve-vmid'),
2913 description
=> "The monitor command.",
2917 returns
=> { type
=> 'string'},
2921 my $rpcenv = PVE
::RPCEnvironment
::get
();
2922 my $authuser = $rpcenv->get_user();
2925 my $command = shift;
2926 return $command =~ m/^\s*info(\s+|$)/
2927 || $command =~ m/^\s*help\s*$/;
2930 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2931 if !&$is_ro($param->{command
});
2933 my $vmid = $param->{vmid
};
2935 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2939 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2941 $res = "ERROR: $@" if $@;
2946 my $guest_agent_commands = [
2954 'network-get-interfaces',
2957 'get-memory-blocks',
2958 'get-memory-block-info',
2965 __PACKAGE__-
>register_method({
2967 path
=> '{vmid}/agent',
2971 description
=> "Execute Qemu Guest Agent commands.",
2973 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2976 additionalProperties
=> 0,
2978 node
=> get_standard_option
('pve-node'),
2979 vmid
=> get_standard_option
('pve-vmid', {
2980 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2983 description
=> "The QGA command.",
2984 enum
=> $guest_agent_commands,
2990 description
=> "Returns an object with a single `result` property. The type of that
2991 property depends on the executed command.",
2996 my $vmid = $param->{vmid
};
2998 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3000 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3001 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3003 my $cmd = $param->{command
};
3005 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3007 return { result
=> $res };
3010 __PACKAGE__-
>register_method({
3011 name
=> 'resize_vm',
3012 path
=> '{vmid}/resize',
3016 description
=> "Extend volume size.",
3018 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3021 additionalProperties
=> 0,
3023 node
=> get_standard_option
('pve-node'),
3024 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3025 skiplock
=> get_standard_option
('skiplock'),
3028 description
=> "The disk you want to resize.",
3029 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3033 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3034 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.",
3038 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3044 returns
=> { type
=> 'null'},
3048 my $rpcenv = PVE
::RPCEnvironment
::get
();
3050 my $authuser = $rpcenv->get_user();
3052 my $node = extract_param
($param, 'node');
3054 my $vmid = extract_param
($param, 'vmid');
3056 my $digest = extract_param
($param, 'digest');
3058 my $disk = extract_param
($param, 'disk');
3060 my $sizestr = extract_param
($param, 'size');
3062 my $skiplock = extract_param
($param, 'skiplock');
3063 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3064 if $skiplock && $authuser ne 'root@pam';
3066 my $storecfg = PVE
::Storage
::config
();
3068 my $updatefn = sub {
3070 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3072 die "checksum missmatch (file change by other user?)\n"
3073 if $digest && $digest ne $conf->{digest
};
3074 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3076 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3078 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3080 my (undef, undef, undef, undef, undef, undef, $format) =
3081 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3083 die "can't resize volume: $disk if snapshot exists\n"
3084 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3086 my $volid = $drive->{file
};
3088 die "disk '$disk' has no associated volume\n" if !$volid;
3090 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3092 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3094 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3096 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3097 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3099 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3100 my ($ext, $newsize, $unit) = ($1, $2, $4);
3103 $newsize = $newsize * 1024;
3104 } elsif ($unit eq 'M') {
3105 $newsize = $newsize * 1024 * 1024;
3106 } elsif ($unit eq 'G') {
3107 $newsize = $newsize * 1024 * 1024 * 1024;
3108 } elsif ($unit eq 'T') {
3109 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3112 $newsize += $size if $ext;
3113 $newsize = int($newsize);
3115 die "shrinking disks is not supported\n" if $newsize < $size;
3117 return if $size == $newsize;
3119 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3121 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3123 $drive->{size
} = $newsize;
3124 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3126 PVE
::QemuConfig-
>write_config($vmid, $conf);
3129 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3133 __PACKAGE__-
>register_method({
3134 name
=> 'snapshot_list',
3135 path
=> '{vmid}/snapshot',
3137 description
=> "List all snapshots.",
3139 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3142 protected
=> 1, # qemu pid files are only readable by root
3144 additionalProperties
=> 0,
3146 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3147 node
=> get_standard_option
('pve-node'),
3156 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3161 my $vmid = $param->{vmid
};
3163 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3164 my $snaphash = $conf->{snapshots
} || {};
3168 foreach my $name (keys %$snaphash) {
3169 my $d = $snaphash->{$name};
3172 snaptime
=> $d->{snaptime
} || 0,
3173 vmstate
=> $d->{vmstate
} ?
1 : 0,
3174 description
=> $d->{description
} || '',
3176 $item->{parent
} = $d->{parent
} if $d->{parent
};
3177 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3181 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3182 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3183 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3185 push @$res, $current;
3190 __PACKAGE__-
>register_method({
3192 path
=> '{vmid}/snapshot',
3196 description
=> "Snapshot a VM.",
3198 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3201 additionalProperties
=> 0,
3203 node
=> get_standard_option
('pve-node'),
3204 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3205 snapname
=> get_standard_option
('pve-snapshot-name'),
3209 description
=> "Save the vmstate",
3214 description
=> "A textual description or comment.",
3220 description
=> "the task ID.",
3225 my $rpcenv = PVE
::RPCEnvironment
::get
();
3227 my $authuser = $rpcenv->get_user();
3229 my $node = extract_param
($param, 'node');
3231 my $vmid = extract_param
($param, 'vmid');
3233 my $snapname = extract_param
($param, 'snapname');
3235 die "unable to use snapshot name 'current' (reserved name)\n"
3236 if $snapname eq 'current';
3239 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3240 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3241 $param->{description
});
3244 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3247 __PACKAGE__-
>register_method({
3248 name
=> 'snapshot_cmd_idx',
3249 path
=> '{vmid}/snapshot/{snapname}',
3256 additionalProperties
=> 0,
3258 vmid
=> get_standard_option
('pve-vmid'),
3259 node
=> get_standard_option
('pve-node'),
3260 snapname
=> get_standard_option
('pve-snapshot-name'),
3269 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3276 push @$res, { cmd
=> 'rollback' };
3277 push @$res, { cmd
=> 'config' };
3282 __PACKAGE__-
>register_method({
3283 name
=> 'update_snapshot_config',
3284 path
=> '{vmid}/snapshot/{snapname}/config',
3288 description
=> "Update snapshot metadata.",
3290 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3293 additionalProperties
=> 0,
3295 node
=> get_standard_option
('pve-node'),
3296 vmid
=> get_standard_option
('pve-vmid'),
3297 snapname
=> get_standard_option
('pve-snapshot-name'),
3301 description
=> "A textual description or comment.",
3305 returns
=> { type
=> 'null' },
3309 my $rpcenv = PVE
::RPCEnvironment
::get
();
3311 my $authuser = $rpcenv->get_user();
3313 my $vmid = extract_param
($param, 'vmid');
3315 my $snapname = extract_param
($param, 'snapname');
3317 return undef if !defined($param->{description
});
3319 my $updatefn = sub {
3321 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3323 PVE
::QemuConfig-
>check_lock($conf);
3325 my $snap = $conf->{snapshots
}->{$snapname};
3327 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3329 $snap->{description
} = $param->{description
} if defined($param->{description
});
3331 PVE
::QemuConfig-
>write_config($vmid, $conf);
3334 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3339 __PACKAGE__-
>register_method({
3340 name
=> 'get_snapshot_config',
3341 path
=> '{vmid}/snapshot/{snapname}/config',
3344 description
=> "Get snapshot configuration",
3346 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3349 additionalProperties
=> 0,
3351 node
=> get_standard_option
('pve-node'),
3352 vmid
=> get_standard_option
('pve-vmid'),
3353 snapname
=> get_standard_option
('pve-snapshot-name'),
3356 returns
=> { type
=> "object" },
3360 my $rpcenv = PVE
::RPCEnvironment
::get
();
3362 my $authuser = $rpcenv->get_user();
3364 my $vmid = extract_param
($param, 'vmid');
3366 my $snapname = extract_param
($param, 'snapname');
3368 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3370 my $snap = $conf->{snapshots
}->{$snapname};
3372 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3377 __PACKAGE__-
>register_method({
3379 path
=> '{vmid}/snapshot/{snapname}/rollback',
3383 description
=> "Rollback VM state to specified snapshot.",
3385 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3388 additionalProperties
=> 0,
3390 node
=> get_standard_option
('pve-node'),
3391 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3392 snapname
=> get_standard_option
('pve-snapshot-name'),
3397 description
=> "the task ID.",
3402 my $rpcenv = PVE
::RPCEnvironment
::get
();
3404 my $authuser = $rpcenv->get_user();
3406 my $node = extract_param
($param, 'node');
3408 my $vmid = extract_param
($param, 'vmid');
3410 my $snapname = extract_param
($param, 'snapname');
3413 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3414 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3417 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3420 __PACKAGE__-
>register_method({
3421 name
=> 'delsnapshot',
3422 path
=> '{vmid}/snapshot/{snapname}',
3426 description
=> "Delete a VM snapshot.",
3428 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3431 additionalProperties
=> 0,
3433 node
=> get_standard_option
('pve-node'),
3434 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3435 snapname
=> get_standard_option
('pve-snapshot-name'),
3439 description
=> "For removal from config file, even if removing disk snapshots fails.",
3445 description
=> "the task ID.",
3450 my $rpcenv = PVE
::RPCEnvironment
::get
();
3452 my $authuser = $rpcenv->get_user();
3454 my $node = extract_param
($param, 'node');
3456 my $vmid = extract_param
($param, 'vmid');
3458 my $snapname = extract_param
($param, 'snapname');
3461 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3462 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3465 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3468 __PACKAGE__-
>register_method({
3470 path
=> '{vmid}/template',
3474 description
=> "Create a Template.",
3476 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3477 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3480 additionalProperties
=> 0,
3482 node
=> get_standard_option
('pve-node'),
3483 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3487 description
=> "If you want to convert only 1 disk to base image.",
3488 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3493 returns
=> { type
=> 'null'},
3497 my $rpcenv = PVE
::RPCEnvironment
::get
();
3499 my $authuser = $rpcenv->get_user();
3501 my $node = extract_param
($param, 'node');
3503 my $vmid = extract_param
($param, 'vmid');
3505 my $disk = extract_param
($param, 'disk');
3507 my $updatefn = sub {
3509 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3511 PVE
::QemuConfig-
>check_lock($conf);
3513 die "unable to create template, because VM contains snapshots\n"
3514 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3516 die "you can't convert a template to a template\n"
3517 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3519 die "you can't convert a VM to template if VM is running\n"
3520 if PVE
::QemuServer
::check_running
($vmid);
3523 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3526 $conf->{template
} = 1;
3527 PVE
::QemuConfig-
>write_config($vmid, $conf);
3529 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3532 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);