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 # return without error if vm has no replica job
1323 PVE
::ReplicationTools
::destroy_replica
($vmid);
1325 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1327 PVE
::AccessControl
::remove_vm_access
($vmid);
1329 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1332 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1335 __PACKAGE__-
>register_method({
1337 path
=> '{vmid}/unlink',
1341 description
=> "Unlink/delete disk images.",
1343 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1346 additionalProperties
=> 0,
1348 node
=> get_standard_option
('pve-node'),
1349 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1351 type
=> 'string', format
=> 'pve-configid-list',
1352 description
=> "A list of disk IDs you want to delete.",
1356 description
=> $opt_force_description,
1361 returns
=> { type
=> 'null'},
1365 $param->{delete} = extract_param
($param, 'idlist');
1367 __PACKAGE__-
>update_vm($param);
1374 __PACKAGE__-
>register_method({
1376 path
=> '{vmid}/vncproxy',
1380 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1382 description
=> "Creates a TCP VNC proxy connections.",
1384 additionalProperties
=> 0,
1386 node
=> get_standard_option
('pve-node'),
1387 vmid
=> get_standard_option
('pve-vmid'),
1391 description
=> "starts websockify instead of vncproxy",
1396 additionalProperties
=> 0,
1398 user
=> { type
=> 'string' },
1399 ticket
=> { type
=> 'string' },
1400 cert
=> { type
=> 'string' },
1401 port
=> { type
=> 'integer' },
1402 upid
=> { type
=> 'string' },
1408 my $rpcenv = PVE
::RPCEnvironment
::get
();
1410 my $authuser = $rpcenv->get_user();
1412 my $vmid = $param->{vmid
};
1413 my $node = $param->{node
};
1414 my $websocket = $param->{websocket
};
1416 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1418 my $authpath = "/vms/$vmid";
1420 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1422 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1425 my ($remip, $family);
1428 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1429 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1430 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1431 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1433 $family = PVE
::Tools
::get_host_address_family
($node);
1436 my $port = PVE
::Tools
::next_vnc_port
($family);
1443 syslog
('info', "starting vnc proxy $upid\n");
1447 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1449 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1451 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1452 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1453 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1454 '-timeout', $timeout, '-authpath', $authpath,
1455 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1456 PVE
::Tools
::run_command
($cmd);
1459 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1461 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1463 my $sock = IO
::Socket
::IP-
>new(
1467 GetAddrInfoFlags
=> 0,
1468 ) or die "failed to create socket: $!\n";
1469 # Inside the worker we shouldn't have any previous alarms
1470 # running anyway...:
1472 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1474 accept(my $cli, $sock) or die "connection failed: $!\n";
1477 if (PVE
::Tools
::run_command
($cmd,
1478 output
=> '>&'.fileno($cli),
1479 input
=> '<&'.fileno($cli),
1482 die "Failed to run vncproxy.\n";
1489 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1491 PVE
::Tools
::wait_for_vnc_port
($port);
1502 __PACKAGE__-
>register_method({
1503 name
=> 'vncwebsocket',
1504 path
=> '{vmid}/vncwebsocket',
1507 description
=> "You also need to pass a valid ticket (vncticket).",
1508 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1510 description
=> "Opens a weksocket for VNC traffic.",
1512 additionalProperties
=> 0,
1514 node
=> get_standard_option
('pve-node'),
1515 vmid
=> get_standard_option
('pve-vmid'),
1517 description
=> "Ticket from previous call to vncproxy.",
1522 description
=> "Port number returned by previous vncproxy call.",
1532 port
=> { type
=> 'string' },
1538 my $rpcenv = PVE
::RPCEnvironment
::get
();
1540 my $authuser = $rpcenv->get_user();
1542 my $vmid = $param->{vmid
};
1543 my $node = $param->{node
};
1545 my $authpath = "/vms/$vmid";
1547 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1549 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1551 # Note: VNC ports are acessible from outside, so we do not gain any
1552 # security if we verify that $param->{port} belongs to VM $vmid. This
1553 # check is done by verifying the VNC ticket (inside VNC protocol).
1555 my $port = $param->{port
};
1557 return { port
=> $port };
1560 __PACKAGE__-
>register_method({
1561 name
=> 'spiceproxy',
1562 path
=> '{vmid}/spiceproxy',
1567 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1569 description
=> "Returns a SPICE configuration to connect to the VM.",
1571 additionalProperties
=> 0,
1573 node
=> get_standard_option
('pve-node'),
1574 vmid
=> get_standard_option
('pve-vmid'),
1575 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1578 returns
=> get_standard_option
('remote-viewer-config'),
1582 my $rpcenv = PVE
::RPCEnvironment
::get
();
1584 my $authuser = $rpcenv->get_user();
1586 my $vmid = $param->{vmid
};
1587 my $node = $param->{node
};
1588 my $proxy = $param->{proxy
};
1590 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1591 my $title = "VM $vmid";
1592 $title .= " - ". $conf->{name
} if $conf->{name
};
1594 my $port = PVE
::QemuServer
::spice_port
($vmid);
1596 my ($ticket, undef, $remote_viewer_config) =
1597 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1599 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1600 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1602 return $remote_viewer_config;
1605 __PACKAGE__-
>register_method({
1607 path
=> '{vmid}/status',
1610 description
=> "Directory index",
1615 additionalProperties
=> 0,
1617 node
=> get_standard_option
('pve-node'),
1618 vmid
=> get_standard_option
('pve-vmid'),
1626 subdir
=> { type
=> 'string' },
1629 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1635 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1638 { subdir
=> 'current' },
1639 { subdir
=> 'start' },
1640 { subdir
=> 'stop' },
1646 __PACKAGE__-
>register_method({
1647 name
=> 'vm_status',
1648 path
=> '{vmid}/status/current',
1651 protected
=> 1, # qemu pid files are only readable by root
1652 description
=> "Get virtual machine status.",
1654 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1657 additionalProperties
=> 0,
1659 node
=> get_standard_option
('pve-node'),
1660 vmid
=> get_standard_option
('pve-vmid'),
1663 returns
=> { type
=> 'object' },
1668 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1670 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1671 my $status = $vmstatus->{$param->{vmid
}};
1673 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1675 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1680 __PACKAGE__-
>register_method({
1682 path
=> '{vmid}/status/start',
1686 description
=> "Start virtual machine.",
1688 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1691 additionalProperties
=> 0,
1693 node
=> get_standard_option
('pve-node'),
1694 vmid
=> get_standard_option
('pve-vmid',
1695 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1696 skiplock
=> get_standard_option
('skiplock'),
1697 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1698 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1701 enum
=> ['secure', 'insecure'],
1702 description
=> "Migration traffic is encrypted using an SSH " .
1703 "tunnel by default. On secure, completely private networks " .
1704 "this can be disabled to increase performance.",
1707 migration_network
=> {
1708 type
=> 'string', format
=> 'CIDR',
1709 description
=> "CIDR of the (sub) network that is used for migration.",
1712 machine
=> get_standard_option
('pve-qm-machine'),
1714 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1726 my $rpcenv = PVE
::RPCEnvironment
::get
();
1728 my $authuser = $rpcenv->get_user();
1730 my $node = extract_param
($param, 'node');
1732 my $vmid = extract_param
($param, 'vmid');
1734 my $machine = extract_param
($param, 'machine');
1736 my $stateuri = extract_param
($param, 'stateuri');
1737 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1738 if $stateuri && $authuser ne 'root@pam';
1740 my $skiplock = extract_param
($param, 'skiplock');
1741 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1742 if $skiplock && $authuser ne 'root@pam';
1744 my $migratedfrom = extract_param
($param, 'migratedfrom');
1745 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1746 if $migratedfrom && $authuser ne 'root@pam';
1748 my $migration_type = extract_param
($param, 'migration_type');
1749 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1750 if $migration_type && $authuser ne 'root@pam';
1752 my $migration_network = extract_param
($param, 'migration_network');
1753 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1754 if $migration_network && $authuser ne 'root@pam';
1756 my $targetstorage = extract_param
($param, 'targetstorage');
1757 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1758 if $targetstorage && $authuser ne 'root@pam';
1760 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1761 if $targetstorage && !$migratedfrom;
1763 # read spice ticket from STDIN
1765 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1766 if (defined(my $line = <>)) {
1768 $spice_ticket = $line;
1772 PVE
::Cluster
::check_cfs_quorum
();
1774 my $storecfg = PVE
::Storage
::config
();
1776 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1777 $rpcenv->{type
} ne 'ha') {
1782 my $service = "vm:$vmid";
1784 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1786 print "Executing HA start for VM $vmid\n";
1788 PVE
::Tools
::run_command
($cmd);
1793 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1800 syslog
('info', "start VM $vmid: $upid\n");
1802 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1803 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1808 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1812 __PACKAGE__-
>register_method({
1814 path
=> '{vmid}/status/stop',
1818 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1819 "is akin to pulling the power plug of a running computer and may damage the VM data",
1821 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1824 additionalProperties
=> 0,
1826 node
=> get_standard_option
('pve-node'),
1827 vmid
=> get_standard_option
('pve-vmid',
1828 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1829 skiplock
=> get_standard_option
('skiplock'),
1830 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1832 description
=> "Wait maximal timeout seconds.",
1838 description
=> "Do not deactivate storage volumes.",
1851 my $rpcenv = PVE
::RPCEnvironment
::get
();
1853 my $authuser = $rpcenv->get_user();
1855 my $node = extract_param
($param, 'node');
1857 my $vmid = extract_param
($param, 'vmid');
1859 my $skiplock = extract_param
($param, 'skiplock');
1860 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1861 if $skiplock && $authuser ne 'root@pam';
1863 my $keepActive = extract_param
($param, 'keepActive');
1864 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1865 if $keepActive && $authuser ne 'root@pam';
1867 my $migratedfrom = extract_param
($param, 'migratedfrom');
1868 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1869 if $migratedfrom && $authuser ne 'root@pam';
1872 my $storecfg = PVE
::Storage
::config
();
1874 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1879 my $service = "vm:$vmid";
1881 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1883 print "Executing HA stop for VM $vmid\n";
1885 PVE
::Tools
::run_command
($cmd);
1890 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1896 syslog
('info', "stop VM $vmid: $upid\n");
1898 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1899 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1904 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1908 __PACKAGE__-
>register_method({
1910 path
=> '{vmid}/status/reset',
1914 description
=> "Reset virtual machine.",
1916 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1919 additionalProperties
=> 0,
1921 node
=> get_standard_option
('pve-node'),
1922 vmid
=> get_standard_option
('pve-vmid',
1923 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1924 skiplock
=> get_standard_option
('skiplock'),
1933 my $rpcenv = PVE
::RPCEnvironment
::get
();
1935 my $authuser = $rpcenv->get_user();
1937 my $node = extract_param
($param, 'node');
1939 my $vmid = extract_param
($param, 'vmid');
1941 my $skiplock = extract_param
($param, 'skiplock');
1942 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1943 if $skiplock && $authuser ne 'root@pam';
1945 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1950 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1955 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1958 __PACKAGE__-
>register_method({
1959 name
=> 'vm_shutdown',
1960 path
=> '{vmid}/status/shutdown',
1964 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1965 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1967 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1970 additionalProperties
=> 0,
1972 node
=> get_standard_option
('pve-node'),
1973 vmid
=> get_standard_option
('pve-vmid',
1974 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1975 skiplock
=> get_standard_option
('skiplock'),
1977 description
=> "Wait maximal timeout seconds.",
1983 description
=> "Make sure the VM stops.",
1989 description
=> "Do not deactivate storage volumes.",
2002 my $rpcenv = PVE
::RPCEnvironment
::get
();
2004 my $authuser = $rpcenv->get_user();
2006 my $node = extract_param
($param, 'node');
2008 my $vmid = extract_param
($param, 'vmid');
2010 my $skiplock = extract_param
($param, 'skiplock');
2011 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2012 if $skiplock && $authuser ne 'root@pam';
2014 my $keepActive = extract_param
($param, 'keepActive');
2015 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2016 if $keepActive && $authuser ne 'root@pam';
2018 my $storecfg = PVE
::Storage
::config
();
2022 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2023 # otherwise, we will infer a shutdown command, but run into the timeout,
2024 # then when the vm is resumed, it will instantly shutdown
2026 # checking the qmp status here to get feedback to the gui/cli/api
2027 # and the status query should not take too long
2030 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2034 if (!$err && $qmpstatus->{status
} eq "paused") {
2035 if ($param->{forceStop
}) {
2036 warn "VM is paused - stop instead of shutdown\n";
2039 die "VM is paused - cannot shutdown\n";
2043 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2044 ($rpcenv->{type
} ne 'ha')) {
2049 my $service = "vm:$vmid";
2051 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2053 print "Executing HA stop for VM $vmid\n";
2055 PVE
::Tools
::run_command
($cmd);
2060 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2067 syslog
('info', "shutdown VM $vmid: $upid\n");
2069 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2070 $shutdown, $param->{forceStop
}, $keepActive);
2075 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2079 __PACKAGE__-
>register_method({
2080 name
=> 'vm_suspend',
2081 path
=> '{vmid}/status/suspend',
2085 description
=> "Suspend virtual machine.",
2087 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2090 additionalProperties
=> 0,
2092 node
=> get_standard_option
('pve-node'),
2093 vmid
=> get_standard_option
('pve-vmid',
2094 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2095 skiplock
=> get_standard_option
('skiplock'),
2104 my $rpcenv = PVE
::RPCEnvironment
::get
();
2106 my $authuser = $rpcenv->get_user();
2108 my $node = extract_param
($param, 'node');
2110 my $vmid = extract_param
($param, 'vmid');
2112 my $skiplock = extract_param
($param, 'skiplock');
2113 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2114 if $skiplock && $authuser ne 'root@pam';
2116 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2121 syslog
('info', "suspend VM $vmid: $upid\n");
2123 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2128 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2131 __PACKAGE__-
>register_method({
2132 name
=> 'vm_resume',
2133 path
=> '{vmid}/status/resume',
2137 description
=> "Resume virtual machine.",
2139 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2142 additionalProperties
=> 0,
2144 node
=> get_standard_option
('pve-node'),
2145 vmid
=> get_standard_option
('pve-vmid',
2146 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2147 skiplock
=> get_standard_option
('skiplock'),
2148 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2158 my $rpcenv = PVE
::RPCEnvironment
::get
();
2160 my $authuser = $rpcenv->get_user();
2162 my $node = extract_param
($param, 'node');
2164 my $vmid = extract_param
($param, 'vmid');
2166 my $skiplock = extract_param
($param, 'skiplock');
2167 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2168 if $skiplock && $authuser ne 'root@pam';
2170 my $nocheck = extract_param
($param, 'nocheck');
2172 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2177 syslog
('info', "resume VM $vmid: $upid\n");
2179 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2184 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2187 __PACKAGE__-
>register_method({
2188 name
=> 'vm_sendkey',
2189 path
=> '{vmid}/sendkey',
2193 description
=> "Send key event to virtual machine.",
2195 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2198 additionalProperties
=> 0,
2200 node
=> get_standard_option
('pve-node'),
2201 vmid
=> get_standard_option
('pve-vmid',
2202 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2203 skiplock
=> get_standard_option
('skiplock'),
2205 description
=> "The key (qemu monitor encoding).",
2210 returns
=> { type
=> 'null'},
2214 my $rpcenv = PVE
::RPCEnvironment
::get
();
2216 my $authuser = $rpcenv->get_user();
2218 my $node = extract_param
($param, 'node');
2220 my $vmid = extract_param
($param, 'vmid');
2222 my $skiplock = extract_param
($param, 'skiplock');
2223 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2224 if $skiplock && $authuser ne 'root@pam';
2226 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2231 __PACKAGE__-
>register_method({
2232 name
=> 'vm_feature',
2233 path
=> '{vmid}/feature',
2237 description
=> "Check if feature for virtual machine is available.",
2239 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2242 additionalProperties
=> 0,
2244 node
=> get_standard_option
('pve-node'),
2245 vmid
=> get_standard_option
('pve-vmid'),
2247 description
=> "Feature to check.",
2249 enum
=> [ 'snapshot', 'clone', 'copy' ],
2251 snapname
=> get_standard_option
('pve-snapshot-name', {
2259 hasFeature
=> { type
=> 'boolean' },
2262 items
=> { type
=> 'string' },
2269 my $node = extract_param
($param, 'node');
2271 my $vmid = extract_param
($param, 'vmid');
2273 my $snapname = extract_param
($param, 'snapname');
2275 my $feature = extract_param
($param, 'feature');
2277 my $running = PVE
::QemuServer
::check_running
($vmid);
2279 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2282 my $snap = $conf->{snapshots
}->{$snapname};
2283 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2286 my $storecfg = PVE
::Storage
::config
();
2288 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2289 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2292 hasFeature
=> $hasFeature,
2293 nodes
=> [ keys %$nodelist ],
2297 __PACKAGE__-
>register_method({
2299 path
=> '{vmid}/clone',
2303 description
=> "Create a copy of virtual machine/template.",
2305 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2306 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2307 "'Datastore.AllocateSpace' on any used storage.",
2310 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2312 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2313 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2318 additionalProperties
=> 0,
2320 node
=> get_standard_option
('pve-node'),
2321 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2322 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2325 type
=> 'string', format
=> 'dns-name',
2326 description
=> "Set a name for the new VM.",
2331 description
=> "Description for the new VM.",
2335 type
=> 'string', format
=> 'pve-poolid',
2336 description
=> "Add the new VM to the specified pool.",
2338 snapname
=> get_standard_option
('pve-snapshot-name', {
2341 storage
=> get_standard_option
('pve-storage-id', {
2342 description
=> "Target storage for full clone.",
2347 description
=> "Target format for file storage.",
2351 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2356 description
=> "Create a full copy of all disk. This is always done when " .
2357 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2360 target
=> get_standard_option
('pve-node', {
2361 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2372 my $rpcenv = PVE
::RPCEnvironment
::get
();
2374 my $authuser = $rpcenv->get_user();
2376 my $node = extract_param
($param, 'node');
2378 my $vmid = extract_param
($param, 'vmid');
2380 my $newid = extract_param
($param, 'newid');
2382 my $pool = extract_param
($param, 'pool');
2384 if (defined($pool)) {
2385 $rpcenv->check_pool_exist($pool);
2388 my $snapname = extract_param
($param, 'snapname');
2390 my $storage = extract_param
($param, 'storage');
2392 my $format = extract_param
($param, 'format');
2394 my $target = extract_param
($param, 'target');
2396 my $localnode = PVE
::INotify
::nodename
();
2398 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2400 PVE
::Cluster
::check_node_exists
($target) if $target;
2402 my $storecfg = PVE
::Storage
::config
();
2405 # check if storage is enabled on local node
2406 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2408 # check if storage is available on target node
2409 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2410 # clone only works if target storage is shared
2411 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2412 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2416 PVE
::Cluster
::check_cfs_quorum
();
2418 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2420 # exclusive lock if VM is running - else shared lock is enough;
2421 my $shared_lock = $running ?
0 : 1;
2425 # do all tests after lock
2426 # we also try to do all tests before we fork the worker
2428 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2430 PVE
::QemuConfig-
>check_lock($conf);
2432 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2434 die "unexpected state change\n" if $verify_running != $running;
2436 die "snapshot '$snapname' does not exist\n"
2437 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2439 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2441 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2443 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2445 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2447 die "unable to create VM $newid: config file already exists\n"
2450 my $newconf = { lock => 'clone' };
2455 foreach my $opt (keys %$oldconf) {
2456 my $value = $oldconf->{$opt};
2458 # do not copy snapshot related info
2459 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2460 $opt eq 'vmstate' || $opt eq 'snapstate';
2462 # no need to copy unused images, because VMID(owner) changes anyways
2463 next if $opt =~ m/^unused\d+$/;
2465 # always change MAC! address
2466 if ($opt =~ m/^net(\d+)$/) {
2467 my $net = PVE
::QemuServer
::parse_net
($value);
2468 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2469 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2470 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2471 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2472 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2473 die "unable to parse drive options for '$opt'\n" if !$drive;
2474 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2475 $newconf->{$opt} = $value; # simply copy configuration
2477 if ($param->{full
}) {
2478 die "Full clone feature is not available"
2479 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2480 $fullclone->{$opt} = 1;
2482 # not full means clone instead of copy
2483 die "Linked clone feature is not available"
2484 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2486 $drives->{$opt} = $drive;
2487 push @$vollist, $drive->{file
};
2490 # copy everything else
2491 $newconf->{$opt} = $value;
2495 # auto generate a new uuid
2496 my ($uuid, $uuid_str);
2497 UUID
::generate
($uuid);
2498 UUID
::unparse
($uuid, $uuid_str);
2499 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2500 $smbios1->{uuid
} = $uuid_str;
2501 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2503 delete $newconf->{template
};
2505 if ($param->{name
}) {
2506 $newconf->{name
} = $param->{name
};
2508 if ($oldconf->{name
}) {
2509 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2511 $newconf->{name
} = "Copy-of-VM-$vmid";
2515 if ($param->{description
}) {
2516 $newconf->{description
} = $param->{description
};
2519 # create empty/temp config - this fails if VM already exists on other node
2520 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2525 my $newvollist = [];
2529 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2531 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2533 my $total_jobs = scalar(keys %{$drives});
2536 foreach my $opt (keys %$drives) {
2537 my $drive = $drives->{$opt};
2538 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2540 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2541 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2542 $jobs, $skipcomplete, $oldconf->{agent
});
2544 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2546 PVE
::QemuConfig-
>write_config($newid, $newconf);
2550 delete $newconf->{lock};
2551 PVE
::QemuConfig-
>write_config($newid, $newconf);
2554 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2555 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2556 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2558 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2559 die "Failed to move config to node '$target' - rename failed: $!\n"
2560 if !rename($conffile, $newconffile);
2563 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2568 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2570 sleep 1; # some storage like rbd need to wait before release volume - really?
2572 foreach my $volid (@$newvollist) {
2573 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2576 die "clone failed: $err";
2582 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2584 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2587 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2588 # Aquire exclusive lock lock for $newid
2589 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2594 __PACKAGE__-
>register_method({
2595 name
=> 'move_vm_disk',
2596 path
=> '{vmid}/move_disk',
2600 description
=> "Move volume to different storage.",
2602 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2604 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2605 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2609 additionalProperties
=> 0,
2611 node
=> get_standard_option
('pve-node'),
2612 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2615 description
=> "The disk you want to move.",
2616 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2618 storage
=> get_standard_option
('pve-storage-id', {
2619 description
=> "Target storage.",
2620 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2624 description
=> "Target Format.",
2625 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2630 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2636 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2644 description
=> "the task ID.",
2649 my $rpcenv = PVE
::RPCEnvironment
::get
();
2651 my $authuser = $rpcenv->get_user();
2653 my $node = extract_param
($param, 'node');
2655 my $vmid = extract_param
($param, 'vmid');
2657 my $digest = extract_param
($param, 'digest');
2659 my $disk = extract_param
($param, 'disk');
2661 my $storeid = extract_param
($param, 'storage');
2663 my $format = extract_param
($param, 'format');
2665 my $storecfg = PVE
::Storage
::config
();
2667 my $updatefn = sub {
2669 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2671 PVE
::QemuConfig-
>check_lock($conf);
2673 die "checksum missmatch (file change by other user?)\n"
2674 if $digest && $digest ne $conf->{digest
};
2676 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2678 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2680 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2682 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2685 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2686 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2690 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2691 (!$format || !$oldfmt || $oldfmt eq $format);
2693 # this only checks snapshots because $disk is passed!
2694 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2695 die "you can't move a disk with snapshots and delete the source\n"
2696 if $snapshotted && $param->{delete};
2698 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2700 my $running = PVE
::QemuServer
::check_running
($vmid);
2702 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2706 my $newvollist = [];
2709 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2711 warn "moving disk with snapshots, snapshots will not be moved!\n"
2714 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2715 $vmid, $storeid, $format, 1, $newvollist);
2717 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2719 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2721 # convert moved disk to base if part of template
2722 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2723 if PVE
::QemuConfig-
>is_template($conf);
2725 PVE
::QemuConfig-
>write_config($vmid, $conf);
2728 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2729 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2736 foreach my $volid (@$newvollist) {
2737 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2740 die "storage migration failed: $err";
2743 if ($param->{delete}) {
2745 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2746 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2752 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2755 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2758 __PACKAGE__-
>register_method({
2759 name
=> 'migrate_vm',
2760 path
=> '{vmid}/migrate',
2764 description
=> "Migrate virtual machine. Creates a new migration task.",
2766 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2769 additionalProperties
=> 0,
2771 node
=> get_standard_option
('pve-node'),
2772 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2773 target
=> get_standard_option
('pve-node', {
2774 description
=> "Target node.",
2775 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2779 description
=> "Use online/live migration.",
2784 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2789 enum
=> ['secure', 'insecure'],
2790 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2793 migration_network
=> {
2794 type
=> 'string', format
=> 'CIDR',
2795 description
=> "CIDR of the (sub) network that is used for migration.",
2798 "with-local-disks" => {
2800 description
=> "Enable live storage migration for local disk",
2803 targetstorage
=> get_standard_option
('pve-storage-id', {
2804 description
=> "Default target storage.",
2806 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2812 description
=> "the task ID.",
2817 my $rpcenv = PVE
::RPCEnvironment
::get
();
2819 my $authuser = $rpcenv->get_user();
2821 my $target = extract_param
($param, 'target');
2823 my $localnode = PVE
::INotify
::nodename
();
2824 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2826 PVE
::Cluster
::check_cfs_quorum
();
2828 PVE
::Cluster
::check_node_exists
($target);
2830 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2832 my $vmid = extract_param
($param, 'vmid');
2834 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2835 if !$param->{online
} && $param->{targetstorage
};
2837 raise_param_exc
({ force
=> "Only root may use this option." })
2838 if $param->{force
} && $authuser ne 'root@pam';
2840 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2841 if $param->{migration_type
} && $authuser ne 'root@pam';
2843 # allow root only until better network permissions are available
2844 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2845 if $param->{migration_network
} && $authuser ne 'root@pam';
2848 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2850 # try to detect errors early
2852 PVE
::QemuConfig-
>check_lock($conf);
2854 if (PVE
::QemuServer
::check_running
($vmid)) {
2855 die "cant migrate running VM without --online\n"
2856 if !$param->{online
};
2859 my $storecfg = PVE
::Storage
::config
();
2861 if( $param->{targetstorage
}) {
2862 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2864 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2867 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2872 my $service = "vm:$vmid";
2874 my $cmd = ['ha-manager', 'migrate', $service, $target];
2876 print "Executing HA migrate for VM $vmid to node $target\n";
2878 PVE
::Tools
::run_command
($cmd);
2883 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2890 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2893 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2898 __PACKAGE__-
>register_method({
2900 path
=> '{vmid}/monitor',
2904 description
=> "Execute Qemu monitor commands.",
2906 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2907 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2910 additionalProperties
=> 0,
2912 node
=> get_standard_option
('pve-node'),
2913 vmid
=> get_standard_option
('pve-vmid'),
2916 description
=> "The monitor command.",
2920 returns
=> { type
=> 'string'},
2924 my $rpcenv = PVE
::RPCEnvironment
::get
();
2925 my $authuser = $rpcenv->get_user();
2928 my $command = shift;
2929 return $command =~ m/^\s*info(\s+|$)/
2930 || $command =~ m/^\s*help\s*$/;
2933 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2934 if !&$is_ro($param->{command
});
2936 my $vmid = $param->{vmid
};
2938 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2942 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2944 $res = "ERROR: $@" if $@;
2949 my $guest_agent_commands = [
2957 'network-get-interfaces',
2960 'get-memory-blocks',
2961 'get-memory-block-info',
2968 __PACKAGE__-
>register_method({
2970 path
=> '{vmid}/agent',
2974 description
=> "Execute Qemu Guest Agent commands.",
2976 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2979 additionalProperties
=> 0,
2981 node
=> get_standard_option
('pve-node'),
2982 vmid
=> get_standard_option
('pve-vmid', {
2983 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2986 description
=> "The QGA command.",
2987 enum
=> $guest_agent_commands,
2993 description
=> "Returns an object with a single `result` property. The type of that
2994 property depends on the executed command.",
2999 my $vmid = $param->{vmid
};
3001 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3003 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3004 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3006 my $cmd = $param->{command
};
3008 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3010 return { result
=> $res };
3013 __PACKAGE__-
>register_method({
3014 name
=> 'resize_vm',
3015 path
=> '{vmid}/resize',
3019 description
=> "Extend volume size.",
3021 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3024 additionalProperties
=> 0,
3026 node
=> get_standard_option
('pve-node'),
3027 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3028 skiplock
=> get_standard_option
('skiplock'),
3031 description
=> "The disk you want to resize.",
3032 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3036 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3037 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.",
3041 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3047 returns
=> { type
=> 'null'},
3051 my $rpcenv = PVE
::RPCEnvironment
::get
();
3053 my $authuser = $rpcenv->get_user();
3055 my $node = extract_param
($param, 'node');
3057 my $vmid = extract_param
($param, 'vmid');
3059 my $digest = extract_param
($param, 'digest');
3061 my $disk = extract_param
($param, 'disk');
3063 my $sizestr = extract_param
($param, 'size');
3065 my $skiplock = extract_param
($param, 'skiplock');
3066 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3067 if $skiplock && $authuser ne 'root@pam';
3069 my $storecfg = PVE
::Storage
::config
();
3071 my $updatefn = sub {
3073 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3075 die "checksum missmatch (file change by other user?)\n"
3076 if $digest && $digest ne $conf->{digest
};
3077 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3079 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3081 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3083 my (undef, undef, undef, undef, undef, undef, $format) =
3084 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3086 die "can't resize volume: $disk if snapshot exists\n"
3087 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3089 my $volid = $drive->{file
};
3091 die "disk '$disk' has no associated volume\n" if !$volid;
3093 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3095 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3097 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3099 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3100 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3102 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3103 my ($ext, $newsize, $unit) = ($1, $2, $4);
3106 $newsize = $newsize * 1024;
3107 } elsif ($unit eq 'M') {
3108 $newsize = $newsize * 1024 * 1024;
3109 } elsif ($unit eq 'G') {
3110 $newsize = $newsize * 1024 * 1024 * 1024;
3111 } elsif ($unit eq 'T') {
3112 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3115 $newsize += $size if $ext;
3116 $newsize = int($newsize);
3118 die "shrinking disks is not supported\n" if $newsize < $size;
3120 return if $size == $newsize;
3122 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3124 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3126 $drive->{size
} = $newsize;
3127 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3129 PVE
::QemuConfig-
>write_config($vmid, $conf);
3132 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3136 __PACKAGE__-
>register_method({
3137 name
=> 'snapshot_list',
3138 path
=> '{vmid}/snapshot',
3140 description
=> "List all snapshots.",
3142 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3145 protected
=> 1, # qemu pid files are only readable by root
3147 additionalProperties
=> 0,
3149 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3150 node
=> get_standard_option
('pve-node'),
3159 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3164 my $vmid = $param->{vmid
};
3166 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3167 my $snaphash = $conf->{snapshots
} || {};
3171 foreach my $name (keys %$snaphash) {
3172 my $d = $snaphash->{$name};
3175 snaptime
=> $d->{snaptime
} || 0,
3176 vmstate
=> $d->{vmstate
} ?
1 : 0,
3177 description
=> $d->{description
} || '',
3179 $item->{parent
} = $d->{parent
} if $d->{parent
};
3180 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3184 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3185 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3186 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3188 push @$res, $current;
3193 __PACKAGE__-
>register_method({
3195 path
=> '{vmid}/snapshot',
3199 description
=> "Snapshot a VM.",
3201 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3204 additionalProperties
=> 0,
3206 node
=> get_standard_option
('pve-node'),
3207 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3208 snapname
=> get_standard_option
('pve-snapshot-name'),
3212 description
=> "Save the vmstate",
3217 description
=> "A textual description or comment.",
3223 description
=> "the task ID.",
3228 my $rpcenv = PVE
::RPCEnvironment
::get
();
3230 my $authuser = $rpcenv->get_user();
3232 my $node = extract_param
($param, 'node');
3234 my $vmid = extract_param
($param, 'vmid');
3236 my $snapname = extract_param
($param, 'snapname');
3238 die "unable to use snapshot name 'current' (reserved name)\n"
3239 if $snapname eq 'current';
3242 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3243 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3244 $param->{description
});
3247 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3250 __PACKAGE__-
>register_method({
3251 name
=> 'snapshot_cmd_idx',
3252 path
=> '{vmid}/snapshot/{snapname}',
3259 additionalProperties
=> 0,
3261 vmid
=> get_standard_option
('pve-vmid'),
3262 node
=> get_standard_option
('pve-node'),
3263 snapname
=> get_standard_option
('pve-snapshot-name'),
3272 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3279 push @$res, { cmd
=> 'rollback' };
3280 push @$res, { cmd
=> 'config' };
3285 __PACKAGE__-
>register_method({
3286 name
=> 'update_snapshot_config',
3287 path
=> '{vmid}/snapshot/{snapname}/config',
3291 description
=> "Update snapshot metadata.",
3293 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3296 additionalProperties
=> 0,
3298 node
=> get_standard_option
('pve-node'),
3299 vmid
=> get_standard_option
('pve-vmid'),
3300 snapname
=> get_standard_option
('pve-snapshot-name'),
3304 description
=> "A textual description or comment.",
3308 returns
=> { type
=> 'null' },
3312 my $rpcenv = PVE
::RPCEnvironment
::get
();
3314 my $authuser = $rpcenv->get_user();
3316 my $vmid = extract_param
($param, 'vmid');
3318 my $snapname = extract_param
($param, 'snapname');
3320 return undef if !defined($param->{description
});
3322 my $updatefn = sub {
3324 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3326 PVE
::QemuConfig-
>check_lock($conf);
3328 my $snap = $conf->{snapshots
}->{$snapname};
3330 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3332 $snap->{description
} = $param->{description
} if defined($param->{description
});
3334 PVE
::QemuConfig-
>write_config($vmid, $conf);
3337 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3342 __PACKAGE__-
>register_method({
3343 name
=> 'get_snapshot_config',
3344 path
=> '{vmid}/snapshot/{snapname}/config',
3347 description
=> "Get snapshot configuration",
3349 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3352 additionalProperties
=> 0,
3354 node
=> get_standard_option
('pve-node'),
3355 vmid
=> get_standard_option
('pve-vmid'),
3356 snapname
=> get_standard_option
('pve-snapshot-name'),
3359 returns
=> { type
=> "object" },
3363 my $rpcenv = PVE
::RPCEnvironment
::get
();
3365 my $authuser = $rpcenv->get_user();
3367 my $vmid = extract_param
($param, 'vmid');
3369 my $snapname = extract_param
($param, 'snapname');
3371 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3373 my $snap = $conf->{snapshots
}->{$snapname};
3375 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3380 __PACKAGE__-
>register_method({
3382 path
=> '{vmid}/snapshot/{snapname}/rollback',
3386 description
=> "Rollback VM state to specified snapshot.",
3388 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3391 additionalProperties
=> 0,
3393 node
=> get_standard_option
('pve-node'),
3394 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3395 snapname
=> get_standard_option
('pve-snapshot-name'),
3400 description
=> "the task ID.",
3405 my $rpcenv = PVE
::RPCEnvironment
::get
();
3407 my $authuser = $rpcenv->get_user();
3409 my $node = extract_param
($param, 'node');
3411 my $vmid = extract_param
($param, 'vmid');
3413 my $snapname = extract_param
($param, 'snapname');
3416 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3417 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3420 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3423 __PACKAGE__-
>register_method({
3424 name
=> 'delsnapshot',
3425 path
=> '{vmid}/snapshot/{snapname}',
3429 description
=> "Delete a VM snapshot.",
3431 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3434 additionalProperties
=> 0,
3436 node
=> get_standard_option
('pve-node'),
3437 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3438 snapname
=> get_standard_option
('pve-snapshot-name'),
3442 description
=> "For removal from config file, even if removing disk snapshots fails.",
3448 description
=> "the task ID.",
3453 my $rpcenv = PVE
::RPCEnvironment
::get
();
3455 my $authuser = $rpcenv->get_user();
3457 my $node = extract_param
($param, 'node');
3459 my $vmid = extract_param
($param, 'vmid');
3461 my $snapname = extract_param
($param, 'snapname');
3464 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3465 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3468 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3471 __PACKAGE__-
>register_method({
3473 path
=> '{vmid}/template',
3477 description
=> "Create a Template.",
3479 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3480 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3483 additionalProperties
=> 0,
3485 node
=> get_standard_option
('pve-node'),
3486 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3490 description
=> "If you want to convert only 1 disk to base image.",
3491 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3496 returns
=> { type
=> 'null'},
3500 my $rpcenv = PVE
::RPCEnvironment
::get
();
3502 my $authuser = $rpcenv->get_user();
3504 my $node = extract_param
($param, 'node');
3506 my $vmid = extract_param
($param, 'vmid');
3508 my $disk = extract_param
($param, 'disk');
3510 my $updatefn = sub {
3512 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3514 PVE
::QemuConfig-
>check_lock($conf);
3516 die "unable to create template, because VM contains snapshots\n"
3517 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3519 die "you can't convert a template to a template\n"
3520 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3522 die "you can't convert a VM to template if VM is running\n"
3523 if PVE
::QemuServer
::check_running
($vmid);
3526 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3529 $conf->{template
} = 1;
3530 PVE
::QemuConfig-
>write_config($vmid, $conf);
3532 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3535 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);