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
::ReplicationTools
::job_remove
($vmid);
1037 PVE
::QemuConfig-
>write_config($vmid, $conf);
1038 } elsif ($opt eq "replica_interval" || $opt eq "replica_rate_limit") {
1039 delete $conf->{$opt};
1040 PVE
::ReplicationTools
::update_conf
($vmid, $opt, $param->{$opt});
1041 PVE
::QemuConfig-
>write_config($vmid, $conf);
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 my $value = $param->{$opt};
1071 PVE
::ReplicationTools
::job_enable
($vmid);
1073 PVE
::ReplicationTools
::job_disable
($vmid);
1075 $conf->{$opt} = $param->{$opt};
1076 } elsif ($opt eq "replica_interval" || $opt eq "replica_rate_limit") {
1077 $conf->{$opt} = $param->{$opt};
1078 PVE
::ReplicationTools
::update_conf
($vmid, $opt, $param->{$opt});
1079 } elsif ($opt eq "replica_target" ) {
1080 die "Node: $param->{$opt} does not exists in Cluster.\n"
1081 if !PVE
::Cluster
::check_node_exists
($param->{$opt});
1082 PVE
::ReplicationTools
::update_conf
($vmid, $opt, $param->{$opt})
1083 if defined($conf->{$opt});
1084 $conf->{$opt} = $param->{$opt};
1086 $conf->{pending
}->{$opt} = $param->{$opt};
1088 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1089 PVE
::QemuConfig-
>write_config($vmid, $conf);
1092 # remove pending changes when nothing changed
1093 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1094 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1095 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1097 return if !scalar(keys %{$conf->{pending
}});
1099 my $running = PVE
::QemuServer
::check_running
($vmid);
1101 # apply pending changes
1103 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1107 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1108 raise_param_exc
($errors) if scalar(keys %$errors);
1110 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1120 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1122 if ($background_delay) {
1124 # Note: It would be better to do that in the Event based HTTPServer
1125 # to avoid blocking call to sleep.
1127 my $end_time = time() + $background_delay;
1129 my $task = PVE
::Tools
::upid_decode
($upid);
1132 while (time() < $end_time) {
1133 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1135 sleep(1); # this gets interrupted when child process ends
1139 my $status = PVE
::Tools
::upid_read_status
($upid);
1140 return undef if $status eq 'OK';
1149 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1152 my $vm_config_perm_list = [
1157 'VM.Config.Network',
1159 'VM.Config.Options',
1162 __PACKAGE__-
>register_method({
1163 name
=> 'update_vm_async',
1164 path
=> '{vmid}/config',
1168 description
=> "Set virtual machine options (asynchrounous API).",
1170 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1173 additionalProperties
=> 0,
1174 properties
=> PVE
::QemuServer
::json_config_properties
(
1176 node
=> get_standard_option
('pve-node'),
1177 vmid
=> get_standard_option
('pve-vmid'),
1178 skiplock
=> get_standard_option
('skiplock'),
1180 type
=> 'string', format
=> 'pve-configid-list',
1181 description
=> "A list of settings you want to delete.",
1185 type
=> 'string', format
=> 'pve-configid-list',
1186 description
=> "Revert a pending change.",
1191 description
=> $opt_force_description,
1193 requires
=> 'delete',
1197 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1201 background_delay
=> {
1203 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1214 code
=> $update_vm_api,
1217 __PACKAGE__-
>register_method({
1218 name
=> 'update_vm',
1219 path
=> '{vmid}/config',
1223 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1225 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1228 additionalProperties
=> 0,
1229 properties
=> PVE
::QemuServer
::json_config_properties
(
1231 node
=> get_standard_option
('pve-node'),
1232 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1233 skiplock
=> get_standard_option
('skiplock'),
1235 type
=> 'string', format
=> 'pve-configid-list',
1236 description
=> "A list of settings you want to delete.",
1240 type
=> 'string', format
=> 'pve-configid-list',
1241 description
=> "Revert a pending change.",
1246 description
=> $opt_force_description,
1248 requires
=> 'delete',
1252 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1258 returns
=> { type
=> 'null' },
1261 &$update_vm_api($param, 1);
1267 __PACKAGE__-
>register_method({
1268 name
=> 'destroy_vm',
1273 description
=> "Destroy the vm (also delete all used/owned volumes).",
1275 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1278 additionalProperties
=> 0,
1280 node
=> get_standard_option
('pve-node'),
1281 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1282 skiplock
=> get_standard_option
('skiplock'),
1291 my $rpcenv = PVE
::RPCEnvironment
::get
();
1293 my $authuser = $rpcenv->get_user();
1295 my $vmid = $param->{vmid
};
1297 my $skiplock = $param->{skiplock
};
1298 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1299 if $skiplock && $authuser ne 'root@pam';
1302 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1304 my $storecfg = PVE
::Storage
::config
();
1306 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1308 die "unable to remove VM $vmid - used in HA resources\n"
1309 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1311 # early tests (repeat after locking)
1312 die "VM $vmid is running - destroy failed\n"
1313 if PVE
::QemuServer
::check_running
($vmid);
1318 syslog
('info', "destroy VM $vmid: $upid\n");
1320 # return without error if vm has no replica job
1321 PVE
::ReplicationTools
::destroy_replica
($vmid);
1323 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1325 PVE
::AccessControl
::remove_vm_access
($vmid);
1327 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1330 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1333 __PACKAGE__-
>register_method({
1335 path
=> '{vmid}/unlink',
1339 description
=> "Unlink/delete disk images.",
1341 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1344 additionalProperties
=> 0,
1346 node
=> get_standard_option
('pve-node'),
1347 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1349 type
=> 'string', format
=> 'pve-configid-list',
1350 description
=> "A list of disk IDs you want to delete.",
1354 description
=> $opt_force_description,
1359 returns
=> { type
=> 'null'},
1363 $param->{delete} = extract_param
($param, 'idlist');
1365 __PACKAGE__-
>update_vm($param);
1372 __PACKAGE__-
>register_method({
1374 path
=> '{vmid}/vncproxy',
1378 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1380 description
=> "Creates a TCP VNC proxy connections.",
1382 additionalProperties
=> 0,
1384 node
=> get_standard_option
('pve-node'),
1385 vmid
=> get_standard_option
('pve-vmid'),
1389 description
=> "starts websockify instead of vncproxy",
1394 additionalProperties
=> 0,
1396 user
=> { type
=> 'string' },
1397 ticket
=> { type
=> 'string' },
1398 cert
=> { type
=> 'string' },
1399 port
=> { type
=> 'integer' },
1400 upid
=> { type
=> 'string' },
1406 my $rpcenv = PVE
::RPCEnvironment
::get
();
1408 my $authuser = $rpcenv->get_user();
1410 my $vmid = $param->{vmid
};
1411 my $node = $param->{node
};
1412 my $websocket = $param->{websocket
};
1414 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1416 my $authpath = "/vms/$vmid";
1418 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1420 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1423 my ($remip, $family);
1426 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1427 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1428 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1429 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1431 $family = PVE
::Tools
::get_host_address_family
($node);
1434 my $port = PVE
::Tools
::next_vnc_port
($family);
1441 syslog
('info', "starting vnc proxy $upid\n");
1445 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1447 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1449 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1450 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1451 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1452 '-timeout', $timeout, '-authpath', $authpath,
1453 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1454 PVE
::Tools
::run_command
($cmd);
1457 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1459 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1461 my $sock = IO
::Socket
::IP-
>new(
1465 GetAddrInfoFlags
=> 0,
1466 ) or die "failed to create socket: $!\n";
1467 # Inside the worker we shouldn't have any previous alarms
1468 # running anyway...:
1470 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1472 accept(my $cli, $sock) or die "connection failed: $!\n";
1475 if (PVE
::Tools
::run_command
($cmd,
1476 output
=> '>&'.fileno($cli),
1477 input
=> '<&'.fileno($cli),
1480 die "Failed to run vncproxy.\n";
1487 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1489 PVE
::Tools
::wait_for_vnc_port
($port);
1500 __PACKAGE__-
>register_method({
1501 name
=> 'vncwebsocket',
1502 path
=> '{vmid}/vncwebsocket',
1505 description
=> "You also need to pass a valid ticket (vncticket).",
1506 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1508 description
=> "Opens a weksocket for VNC traffic.",
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid'),
1515 description
=> "Ticket from previous call to vncproxy.",
1520 description
=> "Port number returned by previous vncproxy call.",
1530 port
=> { type
=> 'string' },
1536 my $rpcenv = PVE
::RPCEnvironment
::get
();
1538 my $authuser = $rpcenv->get_user();
1540 my $vmid = $param->{vmid
};
1541 my $node = $param->{node
};
1543 my $authpath = "/vms/$vmid";
1545 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1547 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1549 # Note: VNC ports are acessible from outside, so we do not gain any
1550 # security if we verify that $param->{port} belongs to VM $vmid. This
1551 # check is done by verifying the VNC ticket (inside VNC protocol).
1553 my $port = $param->{port
};
1555 return { port
=> $port };
1558 __PACKAGE__-
>register_method({
1559 name
=> 'spiceproxy',
1560 path
=> '{vmid}/spiceproxy',
1565 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1567 description
=> "Returns a SPICE configuration to connect to the VM.",
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid'),
1573 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1576 returns
=> get_standard_option
('remote-viewer-config'),
1580 my $rpcenv = PVE
::RPCEnvironment
::get
();
1582 my $authuser = $rpcenv->get_user();
1584 my $vmid = $param->{vmid
};
1585 my $node = $param->{node
};
1586 my $proxy = $param->{proxy
};
1588 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1589 my $title = "VM $vmid";
1590 $title .= " - ". $conf->{name
} if $conf->{name
};
1592 my $port = PVE
::QemuServer
::spice_port
($vmid);
1594 my ($ticket, undef, $remote_viewer_config) =
1595 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1597 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1598 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1600 return $remote_viewer_config;
1603 __PACKAGE__-
>register_method({
1605 path
=> '{vmid}/status',
1608 description
=> "Directory index",
1613 additionalProperties
=> 0,
1615 node
=> get_standard_option
('pve-node'),
1616 vmid
=> get_standard_option
('pve-vmid'),
1624 subdir
=> { type
=> 'string' },
1627 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1633 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1636 { subdir
=> 'current' },
1637 { subdir
=> 'start' },
1638 { subdir
=> 'stop' },
1644 __PACKAGE__-
>register_method({
1645 name
=> 'vm_status',
1646 path
=> '{vmid}/status/current',
1649 protected
=> 1, # qemu pid files are only readable by root
1650 description
=> "Get virtual machine status.",
1652 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1655 additionalProperties
=> 0,
1657 node
=> get_standard_option
('pve-node'),
1658 vmid
=> get_standard_option
('pve-vmid'),
1661 returns
=> { type
=> 'object' },
1666 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1668 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1669 my $status = $vmstatus->{$param->{vmid
}};
1671 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1673 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1678 __PACKAGE__-
>register_method({
1680 path
=> '{vmid}/status/start',
1684 description
=> "Start virtual machine.",
1686 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1689 additionalProperties
=> 0,
1691 node
=> get_standard_option
('pve-node'),
1692 vmid
=> get_standard_option
('pve-vmid',
1693 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1694 skiplock
=> get_standard_option
('skiplock'),
1695 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1696 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1699 enum
=> ['secure', 'insecure'],
1700 description
=> "Migration traffic is encrypted using an SSH " .
1701 "tunnel by default. On secure, completely private networks " .
1702 "this can be disabled to increase performance.",
1705 migration_network
=> {
1706 type
=> 'string', format
=> 'CIDR',
1707 description
=> "CIDR of the (sub) network that is used for migration.",
1710 machine
=> get_standard_option
('pve-qm-machine'),
1712 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1724 my $rpcenv = PVE
::RPCEnvironment
::get
();
1726 my $authuser = $rpcenv->get_user();
1728 my $node = extract_param
($param, 'node');
1730 my $vmid = extract_param
($param, 'vmid');
1732 my $machine = extract_param
($param, 'machine');
1734 my $stateuri = extract_param
($param, 'stateuri');
1735 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1736 if $stateuri && $authuser ne 'root@pam';
1738 my $skiplock = extract_param
($param, 'skiplock');
1739 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1740 if $skiplock && $authuser ne 'root@pam';
1742 my $migratedfrom = extract_param
($param, 'migratedfrom');
1743 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1744 if $migratedfrom && $authuser ne 'root@pam';
1746 my $migration_type = extract_param
($param, 'migration_type');
1747 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1748 if $migration_type && $authuser ne 'root@pam';
1750 my $migration_network = extract_param
($param, 'migration_network');
1751 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1752 if $migration_network && $authuser ne 'root@pam';
1754 my $targetstorage = extract_param
($param, 'targetstorage');
1755 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1756 if $targetstorage && $authuser ne 'root@pam';
1758 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1759 if $targetstorage && !$migratedfrom;
1761 # read spice ticket from STDIN
1763 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1764 if (defined(my $line = <>)) {
1766 $spice_ticket = $line;
1770 PVE
::Cluster
::check_cfs_quorum
();
1772 my $storecfg = PVE
::Storage
::config
();
1774 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1775 $rpcenv->{type
} ne 'ha') {
1780 my $service = "vm:$vmid";
1782 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1784 print "Executing HA start for VM $vmid\n";
1786 PVE
::Tools
::run_command
($cmd);
1791 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1798 syslog
('info', "start VM $vmid: $upid\n");
1800 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1801 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1806 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1810 __PACKAGE__-
>register_method({
1812 path
=> '{vmid}/status/stop',
1816 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1817 "is akin to pulling the power plug of a running computer and may damage the VM data",
1819 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1822 additionalProperties
=> 0,
1824 node
=> get_standard_option
('pve-node'),
1825 vmid
=> get_standard_option
('pve-vmid',
1826 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1827 skiplock
=> get_standard_option
('skiplock'),
1828 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1830 description
=> "Wait maximal timeout seconds.",
1836 description
=> "Do not deactivate storage volumes.",
1849 my $rpcenv = PVE
::RPCEnvironment
::get
();
1851 my $authuser = $rpcenv->get_user();
1853 my $node = extract_param
($param, 'node');
1855 my $vmid = extract_param
($param, 'vmid');
1857 my $skiplock = extract_param
($param, 'skiplock');
1858 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1859 if $skiplock && $authuser ne 'root@pam';
1861 my $keepActive = extract_param
($param, 'keepActive');
1862 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1863 if $keepActive && $authuser ne 'root@pam';
1865 my $migratedfrom = extract_param
($param, 'migratedfrom');
1866 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1867 if $migratedfrom && $authuser ne 'root@pam';
1870 my $storecfg = PVE
::Storage
::config
();
1872 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1877 my $service = "vm:$vmid";
1879 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1881 print "Executing HA stop for VM $vmid\n";
1883 PVE
::Tools
::run_command
($cmd);
1888 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1894 syslog
('info', "stop VM $vmid: $upid\n");
1896 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1897 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1902 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1906 __PACKAGE__-
>register_method({
1908 path
=> '{vmid}/status/reset',
1912 description
=> "Reset virtual machine.",
1914 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1917 additionalProperties
=> 0,
1919 node
=> get_standard_option
('pve-node'),
1920 vmid
=> get_standard_option
('pve-vmid',
1921 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1922 skiplock
=> get_standard_option
('skiplock'),
1931 my $rpcenv = PVE
::RPCEnvironment
::get
();
1933 my $authuser = $rpcenv->get_user();
1935 my $node = extract_param
($param, 'node');
1937 my $vmid = extract_param
($param, 'vmid');
1939 my $skiplock = extract_param
($param, 'skiplock');
1940 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1941 if $skiplock && $authuser ne 'root@pam';
1943 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1948 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1953 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1956 __PACKAGE__-
>register_method({
1957 name
=> 'vm_shutdown',
1958 path
=> '{vmid}/status/shutdown',
1962 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1963 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1965 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1968 additionalProperties
=> 0,
1970 node
=> get_standard_option
('pve-node'),
1971 vmid
=> get_standard_option
('pve-vmid',
1972 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1973 skiplock
=> get_standard_option
('skiplock'),
1975 description
=> "Wait maximal timeout seconds.",
1981 description
=> "Make sure the VM stops.",
1987 description
=> "Do not deactivate storage volumes.",
2000 my $rpcenv = PVE
::RPCEnvironment
::get
();
2002 my $authuser = $rpcenv->get_user();
2004 my $node = extract_param
($param, 'node');
2006 my $vmid = extract_param
($param, 'vmid');
2008 my $skiplock = extract_param
($param, 'skiplock');
2009 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2010 if $skiplock && $authuser ne 'root@pam';
2012 my $keepActive = extract_param
($param, 'keepActive');
2013 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2014 if $keepActive && $authuser ne 'root@pam';
2016 my $storecfg = PVE
::Storage
::config
();
2020 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2021 # otherwise, we will infer a shutdown command, but run into the timeout,
2022 # then when the vm is resumed, it will instantly shutdown
2024 # checking the qmp status here to get feedback to the gui/cli/api
2025 # and the status query should not take too long
2028 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2032 if (!$err && $qmpstatus->{status
} eq "paused") {
2033 if ($param->{forceStop
}) {
2034 warn "VM is paused - stop instead of shutdown\n";
2037 die "VM is paused - cannot shutdown\n";
2041 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2042 ($rpcenv->{type
} ne 'ha')) {
2047 my $service = "vm:$vmid";
2049 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2051 print "Executing HA stop for VM $vmid\n";
2053 PVE
::Tools
::run_command
($cmd);
2058 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2065 syslog
('info', "shutdown VM $vmid: $upid\n");
2067 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2068 $shutdown, $param->{forceStop
}, $keepActive);
2073 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2077 __PACKAGE__-
>register_method({
2078 name
=> 'vm_suspend',
2079 path
=> '{vmid}/status/suspend',
2083 description
=> "Suspend virtual machine.",
2085 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2088 additionalProperties
=> 0,
2090 node
=> get_standard_option
('pve-node'),
2091 vmid
=> get_standard_option
('pve-vmid',
2092 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2093 skiplock
=> get_standard_option
('skiplock'),
2102 my $rpcenv = PVE
::RPCEnvironment
::get
();
2104 my $authuser = $rpcenv->get_user();
2106 my $node = extract_param
($param, 'node');
2108 my $vmid = extract_param
($param, 'vmid');
2110 my $skiplock = extract_param
($param, 'skiplock');
2111 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2112 if $skiplock && $authuser ne 'root@pam';
2114 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2119 syslog
('info', "suspend VM $vmid: $upid\n");
2121 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2126 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2129 __PACKAGE__-
>register_method({
2130 name
=> 'vm_resume',
2131 path
=> '{vmid}/status/resume',
2135 description
=> "Resume virtual machine.",
2137 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2140 additionalProperties
=> 0,
2142 node
=> get_standard_option
('pve-node'),
2143 vmid
=> get_standard_option
('pve-vmid',
2144 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2145 skiplock
=> get_standard_option
('skiplock'),
2146 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2156 my $rpcenv = PVE
::RPCEnvironment
::get
();
2158 my $authuser = $rpcenv->get_user();
2160 my $node = extract_param
($param, 'node');
2162 my $vmid = extract_param
($param, 'vmid');
2164 my $skiplock = extract_param
($param, 'skiplock');
2165 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2166 if $skiplock && $authuser ne 'root@pam';
2168 my $nocheck = extract_param
($param, 'nocheck');
2170 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2175 syslog
('info', "resume VM $vmid: $upid\n");
2177 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2182 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2185 __PACKAGE__-
>register_method({
2186 name
=> 'vm_sendkey',
2187 path
=> '{vmid}/sendkey',
2191 description
=> "Send key event to virtual machine.",
2193 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2196 additionalProperties
=> 0,
2198 node
=> get_standard_option
('pve-node'),
2199 vmid
=> get_standard_option
('pve-vmid',
2200 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2201 skiplock
=> get_standard_option
('skiplock'),
2203 description
=> "The key (qemu monitor encoding).",
2208 returns
=> { type
=> 'null'},
2212 my $rpcenv = PVE
::RPCEnvironment
::get
();
2214 my $authuser = $rpcenv->get_user();
2216 my $node = extract_param
($param, 'node');
2218 my $vmid = extract_param
($param, 'vmid');
2220 my $skiplock = extract_param
($param, 'skiplock');
2221 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2222 if $skiplock && $authuser ne 'root@pam';
2224 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2229 __PACKAGE__-
>register_method({
2230 name
=> 'vm_feature',
2231 path
=> '{vmid}/feature',
2235 description
=> "Check if feature for virtual machine is available.",
2237 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2240 additionalProperties
=> 0,
2242 node
=> get_standard_option
('pve-node'),
2243 vmid
=> get_standard_option
('pve-vmid'),
2245 description
=> "Feature to check.",
2247 enum
=> [ 'snapshot', 'clone', 'copy' ],
2249 snapname
=> get_standard_option
('pve-snapshot-name', {
2257 hasFeature
=> { type
=> 'boolean' },
2260 items
=> { type
=> 'string' },
2267 my $node = extract_param
($param, 'node');
2269 my $vmid = extract_param
($param, 'vmid');
2271 my $snapname = extract_param
($param, 'snapname');
2273 my $feature = extract_param
($param, 'feature');
2275 my $running = PVE
::QemuServer
::check_running
($vmid);
2277 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2280 my $snap = $conf->{snapshots
}->{$snapname};
2281 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2284 my $storecfg = PVE
::Storage
::config
();
2286 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2287 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2290 hasFeature
=> $hasFeature,
2291 nodes
=> [ keys %$nodelist ],
2295 __PACKAGE__-
>register_method({
2297 path
=> '{vmid}/clone',
2301 description
=> "Create a copy of virtual machine/template.",
2303 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2304 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2305 "'Datastore.AllocateSpace' on any used storage.",
2308 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2310 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2311 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2316 additionalProperties
=> 0,
2318 node
=> get_standard_option
('pve-node'),
2319 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2320 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2323 type
=> 'string', format
=> 'dns-name',
2324 description
=> "Set a name for the new VM.",
2329 description
=> "Description for the new VM.",
2333 type
=> 'string', format
=> 'pve-poolid',
2334 description
=> "Add the new VM to the specified pool.",
2336 snapname
=> get_standard_option
('pve-snapshot-name', {
2339 storage
=> get_standard_option
('pve-storage-id', {
2340 description
=> "Target storage for full clone.",
2345 description
=> "Target format for file storage.",
2349 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2354 description
=> "Create a full copy of all disk. This is always done when " .
2355 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2358 target
=> get_standard_option
('pve-node', {
2359 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2370 my $rpcenv = PVE
::RPCEnvironment
::get
();
2372 my $authuser = $rpcenv->get_user();
2374 my $node = extract_param
($param, 'node');
2376 my $vmid = extract_param
($param, 'vmid');
2378 my $newid = extract_param
($param, 'newid');
2380 my $pool = extract_param
($param, 'pool');
2382 if (defined($pool)) {
2383 $rpcenv->check_pool_exist($pool);
2386 my $snapname = extract_param
($param, 'snapname');
2388 my $storage = extract_param
($param, 'storage');
2390 my $format = extract_param
($param, 'format');
2392 my $target = extract_param
($param, 'target');
2394 my $localnode = PVE
::INotify
::nodename
();
2396 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2398 PVE
::Cluster
::check_node_exists
($target) if $target;
2400 my $storecfg = PVE
::Storage
::config
();
2403 # check if storage is enabled on local node
2404 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2406 # check if storage is available on target node
2407 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2408 # clone only works if target storage is shared
2409 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2410 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2414 PVE
::Cluster
::check_cfs_quorum
();
2416 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2418 # exclusive lock if VM is running - else shared lock is enough;
2419 my $shared_lock = $running ?
0 : 1;
2423 # do all tests after lock
2424 # we also try to do all tests before we fork the worker
2426 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2428 PVE
::QemuConfig-
>check_lock($conf);
2430 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2432 die "unexpected state change\n" if $verify_running != $running;
2434 die "snapshot '$snapname' does not exist\n"
2435 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2437 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2439 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2441 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2443 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2445 die "unable to create VM $newid: config file already exists\n"
2448 my $newconf = { lock => 'clone' };
2453 foreach my $opt (keys %$oldconf) {
2454 my $value = $oldconf->{$opt};
2456 # do not copy snapshot related info
2457 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2458 $opt eq 'vmstate' || $opt eq 'snapstate';
2460 # no need to copy unused images, because VMID(owner) changes anyways
2461 next if $opt =~ m/^unused\d+$/;
2463 # always change MAC! address
2464 if ($opt =~ m/^net(\d+)$/) {
2465 my $net = PVE
::QemuServer
::parse_net
($value);
2466 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2467 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2468 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2469 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2470 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2471 die "unable to parse drive options for '$opt'\n" if !$drive;
2472 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2473 $newconf->{$opt} = $value; # simply copy configuration
2475 if ($param->{full
}) {
2476 die "Full clone feature is not available"
2477 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2478 $fullclone->{$opt} = 1;
2480 # not full means clone instead of copy
2481 die "Linked clone feature is not available"
2482 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2484 $drives->{$opt} = $drive;
2485 push @$vollist, $drive->{file
};
2488 # copy everything else
2489 $newconf->{$opt} = $value;
2493 # auto generate a new uuid
2494 my ($uuid, $uuid_str);
2495 UUID
::generate
($uuid);
2496 UUID
::unparse
($uuid, $uuid_str);
2497 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2498 $smbios1->{uuid
} = $uuid_str;
2499 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2501 delete $newconf->{template
};
2503 if ($param->{name
}) {
2504 $newconf->{name
} = $param->{name
};
2506 if ($oldconf->{name
}) {
2507 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2509 $newconf->{name
} = "Copy-of-VM-$vmid";
2513 if ($param->{description
}) {
2514 $newconf->{description
} = $param->{description
};
2517 # create empty/temp config - this fails if VM already exists on other node
2518 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2523 my $newvollist = [];
2527 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2529 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2531 my $total_jobs = scalar(keys %{$drives});
2534 foreach my $opt (keys %$drives) {
2535 my $drive = $drives->{$opt};
2536 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2538 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2539 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2540 $jobs, $skipcomplete, $oldconf->{agent
});
2542 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2544 PVE
::QemuConfig-
>write_config($newid, $newconf);
2548 delete $newconf->{lock};
2549 PVE
::QemuConfig-
>write_config($newid, $newconf);
2552 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2553 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2554 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2556 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2557 die "Failed to move config to node '$target' - rename failed: $!\n"
2558 if !rename($conffile, $newconffile);
2561 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2566 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2568 sleep 1; # some storage like rbd need to wait before release volume - really?
2570 foreach my $volid (@$newvollist) {
2571 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2574 die "clone failed: $err";
2580 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2582 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2585 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2586 # Aquire exclusive lock lock for $newid
2587 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2592 __PACKAGE__-
>register_method({
2593 name
=> 'move_vm_disk',
2594 path
=> '{vmid}/move_disk',
2598 description
=> "Move volume to different storage.",
2600 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2602 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2603 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2607 additionalProperties
=> 0,
2609 node
=> get_standard_option
('pve-node'),
2610 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2613 description
=> "The disk you want to move.",
2614 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2616 storage
=> get_standard_option
('pve-storage-id', {
2617 description
=> "Target storage.",
2618 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2622 description
=> "Target Format.",
2623 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2628 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2634 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2642 description
=> "the task ID.",
2647 my $rpcenv = PVE
::RPCEnvironment
::get
();
2649 my $authuser = $rpcenv->get_user();
2651 my $node = extract_param
($param, 'node');
2653 my $vmid = extract_param
($param, 'vmid');
2655 my $digest = extract_param
($param, 'digest');
2657 my $disk = extract_param
($param, 'disk');
2659 my $storeid = extract_param
($param, 'storage');
2661 my $format = extract_param
($param, 'format');
2663 my $storecfg = PVE
::Storage
::config
();
2665 my $updatefn = sub {
2667 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2669 PVE
::QemuConfig-
>check_lock($conf);
2671 die "checksum missmatch (file change by other user?)\n"
2672 if $digest && $digest ne $conf->{digest
};
2674 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2676 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2678 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2680 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2683 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2684 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2688 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2689 (!$format || !$oldfmt || $oldfmt eq $format);
2691 # this only checks snapshots because $disk is passed!
2692 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2693 die "you can't move a disk with snapshots and delete the source\n"
2694 if $snapshotted && $param->{delete};
2696 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2698 my $running = PVE
::QemuServer
::check_running
($vmid);
2700 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2704 my $newvollist = [];
2707 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2709 warn "moving disk with snapshots, snapshots will not be moved!\n"
2712 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2713 $vmid, $storeid, $format, 1, $newvollist);
2715 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2717 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2719 # convert moved disk to base if part of template
2720 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2721 if PVE
::QemuConfig-
>is_template($conf);
2723 PVE
::QemuConfig-
>write_config($vmid, $conf);
2726 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2727 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2734 foreach my $volid (@$newvollist) {
2735 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2738 die "storage migration failed: $err";
2741 if ($param->{delete}) {
2743 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2744 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2750 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2753 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2756 __PACKAGE__-
>register_method({
2757 name
=> 'migrate_vm',
2758 path
=> '{vmid}/migrate',
2762 description
=> "Migrate virtual machine. Creates a new migration task.",
2764 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2767 additionalProperties
=> 0,
2769 node
=> get_standard_option
('pve-node'),
2770 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2771 target
=> get_standard_option
('pve-node', {
2772 description
=> "Target node.",
2773 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2777 description
=> "Use online/live migration.",
2782 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2787 enum
=> ['secure', 'insecure'],
2788 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2791 migration_network
=> {
2792 type
=> 'string', format
=> 'CIDR',
2793 description
=> "CIDR of the (sub) network that is used for migration.",
2796 "with-local-disks" => {
2798 description
=> "Enable live storage migration for local disk",
2801 targetstorage
=> get_standard_option
('pve-storage-id', {
2802 description
=> "Default target storage.",
2804 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2810 description
=> "the task ID.",
2815 my $rpcenv = PVE
::RPCEnvironment
::get
();
2817 my $authuser = $rpcenv->get_user();
2819 my $target = extract_param
($param, 'target');
2821 my $localnode = PVE
::INotify
::nodename
();
2822 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2824 PVE
::Cluster
::check_cfs_quorum
();
2826 PVE
::Cluster
::check_node_exists
($target);
2828 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2830 my $vmid = extract_param
($param, 'vmid');
2832 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2833 if !$param->{online
} && $param->{targetstorage
};
2835 raise_param_exc
({ force
=> "Only root may use this option." })
2836 if $param->{force
} && $authuser ne 'root@pam';
2838 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2839 if $param->{migration_type
} && $authuser ne 'root@pam';
2841 # allow root only until better network permissions are available
2842 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2843 if $param->{migration_network
} && $authuser ne 'root@pam';
2846 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2848 # try to detect errors early
2850 PVE
::QemuConfig-
>check_lock($conf);
2852 if (PVE
::QemuServer
::check_running
($vmid)) {
2853 die "cant migrate running VM without --online\n"
2854 if !$param->{online
};
2857 my $storecfg = PVE
::Storage
::config
();
2859 if( $param->{targetstorage
}) {
2860 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2862 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2865 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2870 my $service = "vm:$vmid";
2872 my $cmd = ['ha-manager', 'migrate', $service, $target];
2874 print "Executing HA migrate for VM $vmid to node $target\n";
2876 PVE
::Tools
::run_command
($cmd);
2881 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2888 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2891 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2896 __PACKAGE__-
>register_method({
2898 path
=> '{vmid}/monitor',
2902 description
=> "Execute Qemu monitor commands.",
2904 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2905 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2908 additionalProperties
=> 0,
2910 node
=> get_standard_option
('pve-node'),
2911 vmid
=> get_standard_option
('pve-vmid'),
2914 description
=> "The monitor command.",
2918 returns
=> { type
=> 'string'},
2922 my $rpcenv = PVE
::RPCEnvironment
::get
();
2923 my $authuser = $rpcenv->get_user();
2926 my $command = shift;
2927 return $command =~ m/^\s*info(\s+|$)/
2928 || $command =~ m/^\s*help\s*$/;
2931 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2932 if !&$is_ro($param->{command
});
2934 my $vmid = $param->{vmid
};
2936 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2940 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2942 $res = "ERROR: $@" if $@;
2947 my $guest_agent_commands = [
2955 'network-get-interfaces',
2958 'get-memory-blocks',
2959 'get-memory-block-info',
2966 __PACKAGE__-
>register_method({
2968 path
=> '{vmid}/agent',
2972 description
=> "Execute Qemu Guest Agent commands.",
2974 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2977 additionalProperties
=> 0,
2979 node
=> get_standard_option
('pve-node'),
2980 vmid
=> get_standard_option
('pve-vmid', {
2981 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2984 description
=> "The QGA command.",
2985 enum
=> $guest_agent_commands,
2991 description
=> "Returns an object with a single `result` property. The type of that
2992 property depends on the executed command.",
2997 my $vmid = $param->{vmid
};
2999 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3001 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3002 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3004 my $cmd = $param->{command
};
3006 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3008 return { result
=> $res };
3011 __PACKAGE__-
>register_method({
3012 name
=> 'resize_vm',
3013 path
=> '{vmid}/resize',
3017 description
=> "Extend volume size.",
3019 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3022 additionalProperties
=> 0,
3024 node
=> get_standard_option
('pve-node'),
3025 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3026 skiplock
=> get_standard_option
('skiplock'),
3029 description
=> "The disk you want to resize.",
3030 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3034 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3035 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.",
3039 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3045 returns
=> { type
=> 'null'},
3049 my $rpcenv = PVE
::RPCEnvironment
::get
();
3051 my $authuser = $rpcenv->get_user();
3053 my $node = extract_param
($param, 'node');
3055 my $vmid = extract_param
($param, 'vmid');
3057 my $digest = extract_param
($param, 'digest');
3059 my $disk = extract_param
($param, 'disk');
3061 my $sizestr = extract_param
($param, 'size');
3063 my $skiplock = extract_param
($param, 'skiplock');
3064 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3065 if $skiplock && $authuser ne 'root@pam';
3067 my $storecfg = PVE
::Storage
::config
();
3069 my $updatefn = sub {
3071 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3073 die "checksum missmatch (file change by other user?)\n"
3074 if $digest && $digest ne $conf->{digest
};
3075 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3077 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3079 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3081 my (undef, undef, undef, undef, undef, undef, $format) =
3082 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3084 die "can't resize volume: $disk if snapshot exists\n"
3085 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3087 my $volid = $drive->{file
};
3089 die "disk '$disk' has no associated volume\n" if !$volid;
3091 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3093 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3095 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3097 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3098 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3100 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3101 my ($ext, $newsize, $unit) = ($1, $2, $4);
3104 $newsize = $newsize * 1024;
3105 } elsif ($unit eq 'M') {
3106 $newsize = $newsize * 1024 * 1024;
3107 } elsif ($unit eq 'G') {
3108 $newsize = $newsize * 1024 * 1024 * 1024;
3109 } elsif ($unit eq 'T') {
3110 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3113 $newsize += $size if $ext;
3114 $newsize = int($newsize);
3116 die "shrinking disks is not supported\n" if $newsize < $size;
3118 return if $size == $newsize;
3120 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3122 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3124 $drive->{size
} = $newsize;
3125 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3127 PVE
::QemuConfig-
>write_config($vmid, $conf);
3130 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3134 __PACKAGE__-
>register_method({
3135 name
=> 'snapshot_list',
3136 path
=> '{vmid}/snapshot',
3138 description
=> "List all snapshots.",
3140 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3143 protected
=> 1, # qemu pid files are only readable by root
3145 additionalProperties
=> 0,
3147 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3148 node
=> get_standard_option
('pve-node'),
3157 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3162 my $vmid = $param->{vmid
};
3164 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3165 my $snaphash = $conf->{snapshots
} || {};
3169 foreach my $name (keys %$snaphash) {
3170 my $d = $snaphash->{$name};
3173 snaptime
=> $d->{snaptime
} || 0,
3174 vmstate
=> $d->{vmstate
} ?
1 : 0,
3175 description
=> $d->{description
} || '',
3177 $item->{parent
} = $d->{parent
} if $d->{parent
};
3178 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3182 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3183 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3184 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3186 push @$res, $current;
3191 __PACKAGE__-
>register_method({
3193 path
=> '{vmid}/snapshot',
3197 description
=> "Snapshot a VM.",
3199 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3202 additionalProperties
=> 0,
3204 node
=> get_standard_option
('pve-node'),
3205 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3206 snapname
=> get_standard_option
('pve-snapshot-name'),
3210 description
=> "Save the vmstate",
3215 description
=> "A textual description or comment.",
3221 description
=> "the task ID.",
3226 my $rpcenv = PVE
::RPCEnvironment
::get
();
3228 my $authuser = $rpcenv->get_user();
3230 my $node = extract_param
($param, 'node');
3232 my $vmid = extract_param
($param, 'vmid');
3234 my $snapname = extract_param
($param, 'snapname');
3236 die "unable to use snapshot name 'current' (reserved name)\n"
3237 if $snapname eq 'current';
3240 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3241 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3242 $param->{description
});
3245 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3248 __PACKAGE__-
>register_method({
3249 name
=> 'snapshot_cmd_idx',
3250 path
=> '{vmid}/snapshot/{snapname}',
3257 additionalProperties
=> 0,
3259 vmid
=> get_standard_option
('pve-vmid'),
3260 node
=> get_standard_option
('pve-node'),
3261 snapname
=> get_standard_option
('pve-snapshot-name'),
3270 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3277 push @$res, { cmd
=> 'rollback' };
3278 push @$res, { cmd
=> 'config' };
3283 __PACKAGE__-
>register_method({
3284 name
=> 'update_snapshot_config',
3285 path
=> '{vmid}/snapshot/{snapname}/config',
3289 description
=> "Update snapshot metadata.",
3291 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3294 additionalProperties
=> 0,
3296 node
=> get_standard_option
('pve-node'),
3297 vmid
=> get_standard_option
('pve-vmid'),
3298 snapname
=> get_standard_option
('pve-snapshot-name'),
3302 description
=> "A textual description or comment.",
3306 returns
=> { type
=> 'null' },
3310 my $rpcenv = PVE
::RPCEnvironment
::get
();
3312 my $authuser = $rpcenv->get_user();
3314 my $vmid = extract_param
($param, 'vmid');
3316 my $snapname = extract_param
($param, 'snapname');
3318 return undef if !defined($param->{description
});
3320 my $updatefn = sub {
3322 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3324 PVE
::QemuConfig-
>check_lock($conf);
3326 my $snap = $conf->{snapshots
}->{$snapname};
3328 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3330 $snap->{description
} = $param->{description
} if defined($param->{description
});
3332 PVE
::QemuConfig-
>write_config($vmid, $conf);
3335 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3340 __PACKAGE__-
>register_method({
3341 name
=> 'get_snapshot_config',
3342 path
=> '{vmid}/snapshot/{snapname}/config',
3345 description
=> "Get snapshot configuration",
3347 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3350 additionalProperties
=> 0,
3352 node
=> get_standard_option
('pve-node'),
3353 vmid
=> get_standard_option
('pve-vmid'),
3354 snapname
=> get_standard_option
('pve-snapshot-name'),
3357 returns
=> { type
=> "object" },
3361 my $rpcenv = PVE
::RPCEnvironment
::get
();
3363 my $authuser = $rpcenv->get_user();
3365 my $vmid = extract_param
($param, 'vmid');
3367 my $snapname = extract_param
($param, 'snapname');
3369 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3371 my $snap = $conf->{snapshots
}->{$snapname};
3373 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3378 __PACKAGE__-
>register_method({
3380 path
=> '{vmid}/snapshot/{snapname}/rollback',
3384 description
=> "Rollback VM state to specified snapshot.",
3386 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3389 additionalProperties
=> 0,
3391 node
=> get_standard_option
('pve-node'),
3392 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3393 snapname
=> get_standard_option
('pve-snapshot-name'),
3398 description
=> "the task ID.",
3403 my $rpcenv = PVE
::RPCEnvironment
::get
();
3405 my $authuser = $rpcenv->get_user();
3407 my $node = extract_param
($param, 'node');
3409 my $vmid = extract_param
($param, 'vmid');
3411 my $snapname = extract_param
($param, 'snapname');
3414 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3415 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3418 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3421 __PACKAGE__-
>register_method({
3422 name
=> 'delsnapshot',
3423 path
=> '{vmid}/snapshot/{snapname}',
3427 description
=> "Delete a VM snapshot.",
3429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3432 additionalProperties
=> 0,
3434 node
=> get_standard_option
('pve-node'),
3435 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3436 snapname
=> get_standard_option
('pve-snapshot-name'),
3440 description
=> "For removal from config file, even if removing disk snapshots fails.",
3446 description
=> "the task ID.",
3451 my $rpcenv = PVE
::RPCEnvironment
::get
();
3453 my $authuser = $rpcenv->get_user();
3455 my $node = extract_param
($param, 'node');
3457 my $vmid = extract_param
($param, 'vmid');
3459 my $snapname = extract_param
($param, 'snapname');
3462 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3463 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3466 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3469 __PACKAGE__-
>register_method({
3471 path
=> '{vmid}/template',
3475 description
=> "Create a Template.",
3477 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3478 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3481 additionalProperties
=> 0,
3483 node
=> get_standard_option
('pve-node'),
3484 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3488 description
=> "If you want to convert only 1 disk to base image.",
3489 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3494 returns
=> { type
=> 'null'},
3498 my $rpcenv = PVE
::RPCEnvironment
::get
();
3500 my $authuser = $rpcenv->get_user();
3502 my $node = extract_param
($param, 'node');
3504 my $vmid = extract_param
($param, 'vmid');
3506 my $disk = extract_param
($param, 'disk');
3508 my $updatefn = sub {
3510 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3512 PVE
::QemuConfig-
>check_lock($conf);
3514 die "unable to create template, because VM contains snapshots\n"
3515 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3517 die "you can't convert a template to a template\n"
3518 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3520 die "you can't convert a VM to template if VM is running\n"
3521 if PVE
::QemuServer
::check_running
($vmid);
3524 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3527 $conf->{template
} = 1;
3528 PVE
::QemuConfig-
>write_config($vmid, $conf);
3530 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3533 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);