1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::ReplicationConfig
;
19 use PVE
::GuestHelpers
;
23 use PVE
::RPCEnvironment
;
24 use PVE
::AccessControl
;
28 use PVE
::API2
::Firewall
::VM
;
31 if (!$ENV{PVE_GENERATING_DOCS
}) {
32 require PVE
::HA
::Env
::PVE2
;
33 import PVE
::HA
::Env
::PVE2
;
34 require PVE
::HA
::Config
;
35 import PVE
::HA
::Config
;
39 use Data
::Dumper
; # fixme: remove
41 use base
qw(PVE::RESTHandler);
43 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
45 my $resolve_cdrom_alias = sub {
48 if (my $value = $param->{cdrom
}) {
49 $value .= ",media=cdrom" if $value !~ m/media=/;
50 $param->{ide2
} = $value;
51 delete $param->{cdrom
};
55 my $check_storage_access = sub {
56 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
58 PVE
::QemuServer
::foreach_drive
($settings, sub {
59 my ($ds, $drive) = @_;
61 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
63 my $volid = $drive->{file
};
65 if (!$volid || $volid eq 'none') {
67 } elsif ($isCDROM && ($volid eq 'cdrom')) {
68 $rpcenv->check($authuser, "/", ['Sys.Console']);
69 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
70 my ($storeid, $size) = ($2 || $default_storage, $3);
71 die "no storage ID specified (and no default storage)\n" if !$storeid;
72 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
74 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
79 my $check_storage_access_clone = sub {
80 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
84 PVE
::QemuServer
::foreach_drive
($conf, sub {
85 my ($ds, $drive) = @_;
87 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
89 my $volid = $drive->{file
};
91 return if !$volid || $volid eq 'none';
94 if ($volid eq 'cdrom') {
95 $rpcenv->check($authuser, "/", ['Sys.Console']);
97 # we simply allow access
98 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
99 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
100 $sharedvm = 0 if !$scfg->{shared
};
104 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
105 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
106 $sharedvm = 0 if !$scfg->{shared
};
108 $sid = $storage if $storage;
109 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
116 # Note: $pool is only needed when creating a VM, because pool permissions
117 # are automatically inherited if VM already exists inside a pool.
118 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
119 my $create_disks = sub {
120 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
127 my ($ds, $disk) = @_;
129 my $volid = $disk->{file
};
131 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
132 delete $disk->{size
};
133 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
134 } elsif ($volid =~ $NEW_DISK_RE) {
135 my ($storeid, $size) = ($2 || $default_storage, $3);
136 die "no storage ID specified (and no default storage)\n" if !$storeid;
137 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
138 my $fmt = $disk->{format
} || $defformat;
141 if ($ds eq 'efidisk0') {
143 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
144 die "uefi vars image not found\n" if ! -f
$ovmfvars;
145 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
147 $disk->{file
} = $volid;
148 $disk->{size
} = 128*1024;
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
152 my $path = PVE
::Storage
::path
($storecfg, $volid);
153 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
154 push @$efidiskcmd, $ovmfvars;
155 push @$efidiskcmd, $path;
157 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
159 eval { PVE
::Tools
::run_command
($efidiskcmd); };
161 die "Copying of EFI Vars image failed: $err" if $err;
163 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
164 $fmt, undef, $size*1024*1024);
165 $disk->{file
} = $volid;
166 $disk->{size
} = $size*1024*1024*1024;
168 push @$vollist, $volid;
169 delete $disk->{format
}; # no longer needed
170 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
173 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
175 my $volid_is_new = 1;
178 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
179 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
184 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
186 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
188 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
190 die "volume $volid does not exists\n" if !$size;
192 $disk->{size
} = $size;
195 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
199 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
201 # free allocated images on error
203 syslog
('err', "VM $vmid creating disks failed");
204 foreach my $volid (@$vollist) {
205 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
211 # modify vm config if everything went well
212 foreach my $ds (keys %$res) {
213 $conf->{$ds} = $res->{$ds};
230 my $memoryoptions = {
236 my $hwtypeoptions = {
248 my $generaloptions = {
255 'migrate_downtime' => 1,
256 'migrate_speed' => 1,
268 my $vmpoweroptions = {
277 my $check_vm_modify_config_perm = sub {
278 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
280 return 1 if $authuser eq 'root@pam';
282 foreach my $opt (@$key_list) {
283 # disk checks need to be done somewhere else
284 next if PVE
::QemuServer
::is_valid_drivename
($opt);
285 next if $opt eq 'cdrom';
286 next if $opt =~ m/^unused\d+$/;
288 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
289 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
290 } elsif ($memoryoptions->{$opt}) {
291 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
292 } elsif ($hwtypeoptions->{$opt}) {
293 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
294 } elsif ($generaloptions->{$opt}) {
295 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
296 # special case for startup since it changes host behaviour
297 if ($opt eq 'startup') {
298 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
300 } elsif ($vmpoweroptions->{$opt}) {
301 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
302 } elsif ($diskoptions->{$opt}) {
303 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
304 } elsif ($opt =~ m/^net\d+$/) {
305 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
307 # catches usb\d+, hostpci\d+, args, lock, etc.
308 # new options will be checked here
309 die "only root can set '$opt' config\n";
316 __PACKAGE__-
>register_method({
320 description
=> "Virtual machine index (per node).",
322 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
326 protected
=> 1, # qemu pid files are only readable by root
328 additionalProperties
=> 0,
330 node
=> get_standard_option
('pve-node'),
334 description
=> "Determine the full status of active VMs.",
344 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
349 my $rpcenv = PVE
::RPCEnvironment
::get
();
350 my $authuser = $rpcenv->get_user();
352 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
355 foreach my $vmid (keys %$vmstatus) {
356 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
358 my $data = $vmstatus->{$vmid};
359 $data->{vmid
} = int($vmid);
368 __PACKAGE__-
>register_method({
372 description
=> "Create or restore a virtual machine.",
374 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
375 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
376 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
377 user
=> 'all', # check inside
382 additionalProperties
=> 0,
383 properties
=> PVE
::QemuServer
::json_config_properties
(
385 node
=> get_standard_option
('pve-node'),
386 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
388 description
=> "The backup file.",
392 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
394 storage
=> get_standard_option
('pve-storage-id', {
395 description
=> "Default storage.",
397 completion
=> \
&PVE
::QemuServer
::complete_storage
,
402 description
=> "Allow to overwrite existing VM.",
403 requires
=> 'archive',
408 description
=> "Assign a unique random ethernet address.",
409 requires
=> 'archive',
413 type
=> 'string', format
=> 'pve-poolid',
414 description
=> "Add the VM to the specified pool.",
424 my $rpcenv = PVE
::RPCEnvironment
::get
();
426 my $authuser = $rpcenv->get_user();
428 my $node = extract_param
($param, 'node');
430 my $vmid = extract_param
($param, 'vmid');
432 my $archive = extract_param
($param, 'archive');
434 my $storage = extract_param
($param, 'storage');
436 my $force = extract_param
($param, 'force');
438 my $unique = extract_param
($param, 'unique');
440 my $pool = extract_param
($param, 'pool');
442 my $filename = PVE
::QemuConfig-
>config_file($vmid);
444 my $storecfg = PVE
::Storage
::config
();
446 PVE
::Cluster
::check_cfs_quorum
();
448 if (defined($pool)) {
449 $rpcenv->check_pool_exist($pool);
452 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
453 if defined($storage);
455 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
457 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
459 } elsif ($archive && $force && (-f
$filename) &&
460 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
461 # OK: user has VM.Backup permissions, and want to restore an existing VM
467 &$resolve_cdrom_alias($param);
469 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
471 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
473 foreach my $opt (keys %$param) {
474 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
475 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
476 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
478 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
479 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
483 PVE
::QemuServer
::add_random_macs
($param);
485 my $keystr = join(' ', keys %$param);
486 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
488 if ($archive eq '-') {
489 die "pipe requires cli environment\n"
490 if $rpcenv->{type
} ne 'cli';
492 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
493 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
497 my $restorefn = sub {
498 my $vmlist = PVE
::Cluster
::get_vmlist
();
499 if ($vmlist->{ids
}->{$vmid}) {
500 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
501 if ($current_node eq $node) {
502 my $conf = PVE
::QemuConfig-
>load_config($vmid);
504 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
506 die "unable to restore vm $vmid - config file already exists\n"
509 die "unable to restore vm $vmid - vm is running\n"
510 if PVE
::QemuServer
::check_running
($vmid);
512 die "unable to restore vm $vmid - vm is a template\n"
513 if PVE
::QemuConfig-
>is_template($conf);
516 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
521 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
524 unique
=> $unique });
526 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
529 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
535 PVE
::Cluster
::check_vmid_unused
($vmid);
545 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
547 # try to be smart about bootdisk
548 my @disks = PVE
::QemuServer
::valid_drive_names
();
550 foreach my $ds (reverse @disks) {
551 next if !$conf->{$ds};
552 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
553 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
557 if (!$conf->{bootdisk
} && $firstdisk) {
558 $conf->{bootdisk
} = $firstdisk;
561 # auto generate uuid if user did not specify smbios1 option
562 if (!$conf->{smbios1
}) {
563 my ($uuid, $uuid_str);
564 UUID
::generate
($uuid);
565 UUID
::unparse
($uuid, $uuid_str);
566 $conf->{smbios1
} = "uuid=$uuid_str";
569 PVE
::QemuConfig-
>write_config($vmid, $conf);
575 foreach my $volid (@$vollist) {
576 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
579 die "create failed - $err";
582 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
585 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
588 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
591 __PACKAGE__-
>register_method({
596 description
=> "Directory index",
601 additionalProperties
=> 0,
603 node
=> get_standard_option
('pve-node'),
604 vmid
=> get_standard_option
('pve-vmid'),
612 subdir
=> { type
=> 'string' },
615 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
621 { subdir
=> 'config' },
622 { subdir
=> 'pending' },
623 { subdir
=> 'status' },
624 { subdir
=> 'unlink' },
625 { subdir
=> 'vncproxy' },
626 { subdir
=> 'migrate' },
627 { subdir
=> 'resize' },
628 { subdir
=> 'move' },
630 { subdir
=> 'rrddata' },
631 { subdir
=> 'monitor' },
632 { subdir
=> 'agent' },
633 { subdir
=> 'snapshot' },
634 { subdir
=> 'spiceproxy' },
635 { subdir
=> 'sendkey' },
636 { subdir
=> 'firewall' },
642 __PACKAGE__-
>register_method ({
643 subclass
=> "PVE::API2::Firewall::VM",
644 path
=> '{vmid}/firewall',
647 __PACKAGE__-
>register_method({
649 path
=> '{vmid}/rrd',
651 protected
=> 1, # fixme: can we avoid that?
653 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
655 description
=> "Read VM RRD statistics (returns PNG)",
657 additionalProperties
=> 0,
659 node
=> get_standard_option
('pve-node'),
660 vmid
=> get_standard_option
('pve-vmid'),
662 description
=> "Specify the time frame you are interested in.",
664 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
667 description
=> "The list of datasources you want to display.",
668 type
=> 'string', format
=> 'pve-configid-list',
671 description
=> "The RRD consolidation function",
673 enum
=> [ 'AVERAGE', 'MAX' ],
681 filename
=> { type
=> 'string' },
687 return PVE
::Cluster
::create_rrd_graph
(
688 "pve2-vm/$param->{vmid}", $param->{timeframe
},
689 $param->{ds
}, $param->{cf
});
693 __PACKAGE__-
>register_method({
695 path
=> '{vmid}/rrddata',
697 protected
=> 1, # fixme: can we avoid that?
699 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
701 description
=> "Read VM RRD statistics",
703 additionalProperties
=> 0,
705 node
=> get_standard_option
('pve-node'),
706 vmid
=> get_standard_option
('pve-vmid'),
708 description
=> "Specify the time frame you are interested in.",
710 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
713 description
=> "The RRD consolidation function",
715 enum
=> [ 'AVERAGE', 'MAX' ],
730 return PVE
::Cluster
::create_rrd_data
(
731 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
735 __PACKAGE__-
>register_method({
737 path
=> '{vmid}/config',
740 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
745 additionalProperties
=> 0,
747 node
=> get_standard_option
('pve-node'),
748 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
750 description
=> "Get current values (instead of pending values).",
762 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
769 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
771 delete $conf->{snapshots
};
773 if (!$param->{current
}) {
774 foreach my $opt (keys %{$conf->{pending
}}) {
775 next if $opt eq 'delete';
776 my $value = $conf->{pending
}->{$opt};
777 next if ref($value); # just to be sure
778 $conf->{$opt} = $value;
780 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
781 foreach my $opt (keys %$pending_delete_hash) {
782 delete $conf->{$opt} if $conf->{$opt};
786 delete $conf->{pending
};
791 __PACKAGE__-
>register_method({
792 name
=> 'vm_pending',
793 path
=> '{vmid}/pending',
796 description
=> "Get virtual machine configuration, including pending changes.",
798 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
801 additionalProperties
=> 0,
803 node
=> get_standard_option
('pve-node'),
804 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
813 description
=> "Configuration option name.",
817 description
=> "Current value.",
822 description
=> "Pending value.",
827 description
=> "Indicates a pending delete request if present and not 0. " .
828 "The value 2 indicates a force-delete request.",
840 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
842 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
846 foreach my $opt (keys %$conf) {
847 next if ref($conf->{$opt});
848 my $item = { key
=> $opt };
849 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
850 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
851 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
855 foreach my $opt (keys %{$conf->{pending
}}) {
856 next if $opt eq 'delete';
857 next if ref($conf->{pending
}->{$opt}); # just to be sure
858 next if defined($conf->{$opt});
859 my $item = { key
=> $opt };
860 $item->{pending
} = $conf->{pending
}->{$opt};
864 while (my ($opt, $force) = each %$pending_delete_hash) {
865 next if $conf->{pending
}->{$opt}; # just to be sure
866 next if $conf->{$opt};
867 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
874 # POST/PUT {vmid}/config implementation
876 # The original API used PUT (idempotent) an we assumed that all operations
877 # are fast. But it turned out that almost any configuration change can
878 # involve hot-plug actions, or disk alloc/free. Such actions can take long
879 # time to complete and have side effects (not idempotent).
881 # The new implementation uses POST and forks a worker process. We added
882 # a new option 'background_delay'. If specified we wait up to
883 # 'background_delay' second for the worker task to complete. It returns null
884 # if the task is finished within that time, else we return the UPID.
886 my $update_vm_api = sub {
887 my ($param, $sync) = @_;
889 my $rpcenv = PVE
::RPCEnvironment
::get
();
891 my $authuser = $rpcenv->get_user();
893 my $node = extract_param
($param, 'node');
895 my $vmid = extract_param
($param, 'vmid');
897 my $digest = extract_param
($param, 'digest');
899 my $background_delay = extract_param
($param, 'background_delay');
901 my @paramarr = (); # used for log message
902 foreach my $key (keys %$param) {
903 push @paramarr, "-$key", $param->{$key};
906 my $skiplock = extract_param
($param, 'skiplock');
907 raise_param_exc
({ skiplock
=> "Only root may use this option." })
908 if $skiplock && $authuser ne 'root@pam';
910 my $delete_str = extract_param
($param, 'delete');
912 my $revert_str = extract_param
($param, 'revert');
914 my $force = extract_param
($param, 'force');
916 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
918 my $storecfg = PVE
::Storage
::config
();
920 my $defaults = PVE
::QemuServer
::load_defaults
();
922 &$resolve_cdrom_alias($param);
924 # now try to verify all parameters
927 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
928 if (!PVE
::QemuServer
::option_exists
($opt)) {
929 raise_param_exc
({ revert
=> "unknown option '$opt'" });
932 raise_param_exc
({ delete => "you can't use '-$opt' and " .
933 "-revert $opt' at the same time" })
934 if defined($param->{$opt});
940 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
941 $opt = 'ide2' if $opt eq 'cdrom';
943 raise_param_exc
({ delete => "you can't use '-$opt' and " .
944 "-delete $opt' at the same time" })
945 if defined($param->{$opt});
947 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
948 "-revert $opt' at the same time" })
951 if (!PVE
::QemuServer
::option_exists
($opt)) {
952 raise_param_exc
({ delete => "unknown option '$opt'" });
958 my $repl_conf = PVE
::ReplicationConfig-
>new();
959 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
960 my $check_replication = sub {
962 return if !$is_replicated;
963 my $volid = $drive->{file
};
964 return if !$volid || !($drive->{replicate
}//1);
965 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
966 my ($storeid, $format);
967 if ($volid =~ $NEW_DISK_RE) {
969 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
971 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
972 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
974 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
975 die "cannot add non-replicatable volume to a replicated VM\n";
978 foreach my $opt (keys %$param) {
979 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
981 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
982 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
983 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
984 $check_replication->($drive);
985 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
986 } elsif ($opt =~ m/^net(\d+)$/) {
988 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
989 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
993 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
995 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
997 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1001 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1003 die "checksum missmatch (file change by other user?)\n"
1004 if $digest && $digest ne $conf->{digest
};
1006 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1008 foreach my $opt (keys %$revert) {
1009 if (defined($conf->{$opt})) {
1010 $param->{$opt} = $conf->{$opt};
1011 } elsif (defined($conf->{pending
}->{$opt})) {
1016 if ($param->{memory
} || defined($param->{balloon
})) {
1017 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1018 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1020 die "balloon value too large (must be smaller than assigned memory)\n"
1021 if $balloon && $balloon > $maxmem;
1024 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1028 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1030 # write updates to pending section
1032 my $modified = {}; # record what $option we modify
1034 foreach my $opt (@delete) {
1035 $modified->{$opt} = 1;
1036 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1037 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1038 warn "cannot delete '$opt' - not set in current configuration!\n";
1039 $modified->{$opt} = 0;
1043 if ($opt =~ m/^unused/) {
1044 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1045 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1046 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1047 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1048 delete $conf->{$opt};
1049 PVE
::QemuConfig-
>write_config($vmid, $conf);
1051 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1052 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1053 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1054 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1055 if defined($conf->{pending
}->{$opt});
1056 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1057 PVE
::QemuConfig-
>write_config($vmid, $conf);
1059 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1060 PVE
::QemuConfig-
>write_config($vmid, $conf);
1064 foreach my $opt (keys %$param) { # add/change
1065 $modified->{$opt} = 1;
1066 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1067 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1069 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1070 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1071 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1072 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1074 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1076 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1077 if defined($conf->{pending
}->{$opt});
1079 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1081 $conf->{pending
}->{$opt} = $param->{$opt};
1083 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1084 PVE
::QemuConfig-
>write_config($vmid, $conf);
1087 # remove pending changes when nothing changed
1088 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1089 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1090 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1092 return if !scalar(keys %{$conf->{pending
}});
1094 my $running = PVE
::QemuServer
::check_running
($vmid);
1096 # apply pending changes
1098 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1102 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1103 raise_param_exc
($errors) if scalar(keys %$errors);
1105 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1115 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1117 if ($background_delay) {
1119 # Note: It would be better to do that in the Event based HTTPServer
1120 # to avoid blocking call to sleep.
1122 my $end_time = time() + $background_delay;
1124 my $task = PVE
::Tools
::upid_decode
($upid);
1127 while (time() < $end_time) {
1128 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1130 sleep(1); # this gets interrupted when child process ends
1134 my $status = PVE
::Tools
::upid_read_status
($upid);
1135 return undef if $status eq 'OK';
1144 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1147 my $vm_config_perm_list = [
1152 'VM.Config.Network',
1154 'VM.Config.Options',
1157 __PACKAGE__-
>register_method({
1158 name
=> 'update_vm_async',
1159 path
=> '{vmid}/config',
1163 description
=> "Set virtual machine options (asynchrounous API).",
1165 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1168 additionalProperties
=> 0,
1169 properties
=> PVE
::QemuServer
::json_config_properties
(
1171 node
=> get_standard_option
('pve-node'),
1172 vmid
=> get_standard_option
('pve-vmid'),
1173 skiplock
=> get_standard_option
('skiplock'),
1175 type
=> 'string', format
=> 'pve-configid-list',
1176 description
=> "A list of settings you want to delete.",
1180 type
=> 'string', format
=> 'pve-configid-list',
1181 description
=> "Revert a pending change.",
1186 description
=> $opt_force_description,
1188 requires
=> 'delete',
1192 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1196 background_delay
=> {
1198 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1209 code
=> $update_vm_api,
1212 __PACKAGE__-
>register_method({
1213 name
=> 'update_vm',
1214 path
=> '{vmid}/config',
1218 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1220 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1223 additionalProperties
=> 0,
1224 properties
=> PVE
::QemuServer
::json_config_properties
(
1226 node
=> get_standard_option
('pve-node'),
1227 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1228 skiplock
=> get_standard_option
('skiplock'),
1230 type
=> 'string', format
=> 'pve-configid-list',
1231 description
=> "A list of settings you want to delete.",
1235 type
=> 'string', format
=> 'pve-configid-list',
1236 description
=> "Revert a pending change.",
1241 description
=> $opt_force_description,
1243 requires
=> 'delete',
1247 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1253 returns
=> { type
=> 'null' },
1256 &$update_vm_api($param, 1);
1262 __PACKAGE__-
>register_method({
1263 name
=> 'destroy_vm',
1268 description
=> "Destroy the vm (also delete all used/owned volumes).",
1270 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1273 additionalProperties
=> 0,
1275 node
=> get_standard_option
('pve-node'),
1276 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1277 skiplock
=> get_standard_option
('skiplock'),
1286 my $rpcenv = PVE
::RPCEnvironment
::get
();
1288 my $authuser = $rpcenv->get_user();
1290 my $vmid = $param->{vmid
};
1292 my $skiplock = $param->{skiplock
};
1293 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1294 if $skiplock && $authuser ne 'root@pam';
1297 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1299 my $storecfg = PVE
::Storage
::config
();
1301 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1303 die "unable to remove VM $vmid - used in HA resources\n"
1304 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1306 # do not allow destroy if there are replication jobs
1307 my $repl_conf = PVE
::ReplicationConfig-
>new();
1308 $repl_conf->check_for_existing_jobs($vmid);
1310 # early tests (repeat after locking)
1311 die "VM $vmid is running - destroy failed\n"
1312 if PVE
::QemuServer
::check_running
($vmid);
1317 syslog
('info', "destroy VM $vmid: $upid\n");
1319 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1321 PVE
::AccessControl
::remove_vm_access
($vmid);
1323 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1326 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1329 __PACKAGE__-
>register_method({
1331 path
=> '{vmid}/unlink',
1335 description
=> "Unlink/delete disk images.",
1337 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1340 additionalProperties
=> 0,
1342 node
=> get_standard_option
('pve-node'),
1343 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1345 type
=> 'string', format
=> 'pve-configid-list',
1346 description
=> "A list of disk IDs you want to delete.",
1350 description
=> $opt_force_description,
1355 returns
=> { type
=> 'null'},
1359 $param->{delete} = extract_param
($param, 'idlist');
1361 __PACKAGE__-
>update_vm($param);
1368 __PACKAGE__-
>register_method({
1370 path
=> '{vmid}/vncproxy',
1374 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1376 description
=> "Creates a TCP VNC proxy connections.",
1378 additionalProperties
=> 0,
1380 node
=> get_standard_option
('pve-node'),
1381 vmid
=> get_standard_option
('pve-vmid'),
1385 description
=> "starts websockify instead of vncproxy",
1390 additionalProperties
=> 0,
1392 user
=> { type
=> 'string' },
1393 ticket
=> { type
=> 'string' },
1394 cert
=> { type
=> 'string' },
1395 port
=> { type
=> 'integer' },
1396 upid
=> { type
=> 'string' },
1402 my $rpcenv = PVE
::RPCEnvironment
::get
();
1404 my $authuser = $rpcenv->get_user();
1406 my $vmid = $param->{vmid
};
1407 my $node = $param->{node
};
1408 my $websocket = $param->{websocket
};
1410 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1412 my $authpath = "/vms/$vmid";
1414 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1416 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1419 my ($remip, $family);
1422 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1423 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1424 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1425 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1427 $family = PVE
::Tools
::get_host_address_family
($node);
1430 my $port = PVE
::Tools
::next_vnc_port
($family);
1437 syslog
('info', "starting vnc proxy $upid\n");
1441 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1443 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1445 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1446 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1447 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1448 '-timeout', $timeout, '-authpath', $authpath,
1449 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1450 PVE
::Tools
::run_command
($cmd);
1453 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1455 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1457 my $sock = IO
::Socket
::IP-
>new(
1462 GetAddrInfoFlags
=> 0,
1463 ) or die "failed to create socket: $!\n";
1464 # Inside the worker we shouldn't have any previous alarms
1465 # running anyway...:
1467 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1469 accept(my $cli, $sock) or die "connection failed: $!\n";
1472 if (PVE
::Tools
::run_command
($cmd,
1473 output
=> '>&'.fileno($cli),
1474 input
=> '<&'.fileno($cli),
1477 die "Failed to run vncproxy.\n";
1484 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1486 PVE
::Tools
::wait_for_vnc_port
($port);
1497 __PACKAGE__-
>register_method({
1498 name
=> 'vncwebsocket',
1499 path
=> '{vmid}/vncwebsocket',
1502 description
=> "You also need to pass a valid ticket (vncticket).",
1503 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1505 description
=> "Opens a weksocket for VNC traffic.",
1507 additionalProperties
=> 0,
1509 node
=> get_standard_option
('pve-node'),
1510 vmid
=> get_standard_option
('pve-vmid'),
1512 description
=> "Ticket from previous call to vncproxy.",
1517 description
=> "Port number returned by previous vncproxy call.",
1527 port
=> { type
=> 'string' },
1533 my $rpcenv = PVE
::RPCEnvironment
::get
();
1535 my $authuser = $rpcenv->get_user();
1537 my $vmid = $param->{vmid
};
1538 my $node = $param->{node
};
1540 my $authpath = "/vms/$vmid";
1542 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1544 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1546 # Note: VNC ports are acessible from outside, so we do not gain any
1547 # security if we verify that $param->{port} belongs to VM $vmid. This
1548 # check is done by verifying the VNC ticket (inside VNC protocol).
1550 my $port = $param->{port
};
1552 return { port
=> $port };
1555 __PACKAGE__-
>register_method({
1556 name
=> 'spiceproxy',
1557 path
=> '{vmid}/spiceproxy',
1562 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1564 description
=> "Returns a SPICE configuration to connect to the VM.",
1566 additionalProperties
=> 0,
1568 node
=> get_standard_option
('pve-node'),
1569 vmid
=> get_standard_option
('pve-vmid'),
1570 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1573 returns
=> get_standard_option
('remote-viewer-config'),
1577 my $rpcenv = PVE
::RPCEnvironment
::get
();
1579 my $authuser = $rpcenv->get_user();
1581 my $vmid = $param->{vmid
};
1582 my $node = $param->{node
};
1583 my $proxy = $param->{proxy
};
1585 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1586 my $title = "VM $vmid";
1587 $title .= " - ". $conf->{name
} if $conf->{name
};
1589 my $port = PVE
::QemuServer
::spice_port
($vmid);
1591 my ($ticket, undef, $remote_viewer_config) =
1592 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1594 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1595 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1597 return $remote_viewer_config;
1600 __PACKAGE__-
>register_method({
1602 path
=> '{vmid}/status',
1605 description
=> "Directory index",
1610 additionalProperties
=> 0,
1612 node
=> get_standard_option
('pve-node'),
1613 vmid
=> get_standard_option
('pve-vmid'),
1621 subdir
=> { type
=> 'string' },
1624 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1630 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1633 { subdir
=> 'current' },
1634 { subdir
=> 'start' },
1635 { subdir
=> 'stop' },
1641 __PACKAGE__-
>register_method({
1642 name
=> 'vm_status',
1643 path
=> '{vmid}/status/current',
1646 protected
=> 1, # qemu pid files are only readable by root
1647 description
=> "Get virtual machine status.",
1649 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1652 additionalProperties
=> 0,
1654 node
=> get_standard_option
('pve-node'),
1655 vmid
=> get_standard_option
('pve-vmid'),
1658 returns
=> { type
=> 'object' },
1663 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1665 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1666 my $status = $vmstatus->{$param->{vmid
}};
1668 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1670 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1675 __PACKAGE__-
>register_method({
1677 path
=> '{vmid}/status/start',
1681 description
=> "Start virtual machine.",
1683 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1686 additionalProperties
=> 0,
1688 node
=> get_standard_option
('pve-node'),
1689 vmid
=> get_standard_option
('pve-vmid',
1690 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1691 skiplock
=> get_standard_option
('skiplock'),
1692 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1693 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1696 enum
=> ['secure', 'insecure'],
1697 description
=> "Migration traffic is encrypted using an SSH " .
1698 "tunnel by default. On secure, completely private networks " .
1699 "this can be disabled to increase performance.",
1702 migration_network
=> {
1703 type
=> 'string', format
=> 'CIDR',
1704 description
=> "CIDR of the (sub) network that is used for migration.",
1707 machine
=> get_standard_option
('pve-qm-machine'),
1709 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1721 my $rpcenv = PVE
::RPCEnvironment
::get
();
1723 my $authuser = $rpcenv->get_user();
1725 my $node = extract_param
($param, 'node');
1727 my $vmid = extract_param
($param, 'vmid');
1729 my $machine = extract_param
($param, 'machine');
1731 my $stateuri = extract_param
($param, 'stateuri');
1732 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1733 if $stateuri && $authuser ne 'root@pam';
1735 my $skiplock = extract_param
($param, 'skiplock');
1736 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1737 if $skiplock && $authuser ne 'root@pam';
1739 my $migratedfrom = extract_param
($param, 'migratedfrom');
1740 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1741 if $migratedfrom && $authuser ne 'root@pam';
1743 my $migration_type = extract_param
($param, 'migration_type');
1744 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1745 if $migration_type && $authuser ne 'root@pam';
1747 my $migration_network = extract_param
($param, 'migration_network');
1748 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1749 if $migration_network && $authuser ne 'root@pam';
1751 my $targetstorage = extract_param
($param, 'targetstorage');
1752 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1753 if $targetstorage && $authuser ne 'root@pam';
1755 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1756 if $targetstorage && !$migratedfrom;
1758 # read spice ticket from STDIN
1760 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1761 if (defined(my $line = <>)) {
1763 $spice_ticket = $line;
1767 PVE
::Cluster
::check_cfs_quorum
();
1769 my $storecfg = PVE
::Storage
::config
();
1771 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1772 $rpcenv->{type
} ne 'ha') {
1777 my $service = "vm:$vmid";
1779 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1781 print "Requesting HA start for VM $vmid\n";
1783 PVE
::Tools
::run_command
($cmd);
1788 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1795 syslog
('info', "start VM $vmid: $upid\n");
1797 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1798 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1803 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1807 __PACKAGE__-
>register_method({
1809 path
=> '{vmid}/status/stop',
1813 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1814 "is akin to pulling the power plug of a running computer and may damage the VM data",
1816 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1819 additionalProperties
=> 0,
1821 node
=> get_standard_option
('pve-node'),
1822 vmid
=> get_standard_option
('pve-vmid',
1823 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1824 skiplock
=> get_standard_option
('skiplock'),
1825 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1827 description
=> "Wait maximal timeout seconds.",
1833 description
=> "Do not deactivate storage volumes.",
1846 my $rpcenv = PVE
::RPCEnvironment
::get
();
1848 my $authuser = $rpcenv->get_user();
1850 my $node = extract_param
($param, 'node');
1852 my $vmid = extract_param
($param, 'vmid');
1854 my $skiplock = extract_param
($param, 'skiplock');
1855 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1856 if $skiplock && $authuser ne 'root@pam';
1858 my $keepActive = extract_param
($param, 'keepActive');
1859 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1860 if $keepActive && $authuser ne 'root@pam';
1862 my $migratedfrom = extract_param
($param, 'migratedfrom');
1863 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1864 if $migratedfrom && $authuser ne 'root@pam';
1867 my $storecfg = PVE
::Storage
::config
();
1869 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1874 my $service = "vm:$vmid";
1876 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1878 print "Requesting HA stop for VM $vmid\n";
1880 PVE
::Tools
::run_command
($cmd);
1885 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1891 syslog
('info', "stop VM $vmid: $upid\n");
1893 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1894 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1899 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1903 __PACKAGE__-
>register_method({
1905 path
=> '{vmid}/status/reset',
1909 description
=> "Reset virtual machine.",
1911 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1914 additionalProperties
=> 0,
1916 node
=> get_standard_option
('pve-node'),
1917 vmid
=> get_standard_option
('pve-vmid',
1918 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1919 skiplock
=> get_standard_option
('skiplock'),
1928 my $rpcenv = PVE
::RPCEnvironment
::get
();
1930 my $authuser = $rpcenv->get_user();
1932 my $node = extract_param
($param, 'node');
1934 my $vmid = extract_param
($param, 'vmid');
1936 my $skiplock = extract_param
($param, 'skiplock');
1937 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1938 if $skiplock && $authuser ne 'root@pam';
1940 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1945 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1950 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1953 __PACKAGE__-
>register_method({
1954 name
=> 'vm_shutdown',
1955 path
=> '{vmid}/status/shutdown',
1959 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1960 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1962 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1965 additionalProperties
=> 0,
1967 node
=> get_standard_option
('pve-node'),
1968 vmid
=> get_standard_option
('pve-vmid',
1969 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1970 skiplock
=> get_standard_option
('skiplock'),
1972 description
=> "Wait maximal timeout seconds.",
1978 description
=> "Make sure the VM stops.",
1984 description
=> "Do not deactivate storage volumes.",
1997 my $rpcenv = PVE
::RPCEnvironment
::get
();
1999 my $authuser = $rpcenv->get_user();
2001 my $node = extract_param
($param, 'node');
2003 my $vmid = extract_param
($param, 'vmid');
2005 my $skiplock = extract_param
($param, 'skiplock');
2006 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2007 if $skiplock && $authuser ne 'root@pam';
2009 my $keepActive = extract_param
($param, 'keepActive');
2010 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2011 if $keepActive && $authuser ne 'root@pam';
2013 my $storecfg = PVE
::Storage
::config
();
2017 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2018 # otherwise, we will infer a shutdown command, but run into the timeout,
2019 # then when the vm is resumed, it will instantly shutdown
2021 # checking the qmp status here to get feedback to the gui/cli/api
2022 # and the status query should not take too long
2025 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2029 if (!$err && $qmpstatus->{status
} eq "paused") {
2030 if ($param->{forceStop
}) {
2031 warn "VM is paused - stop instead of shutdown\n";
2034 die "VM is paused - cannot shutdown\n";
2038 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2039 ($rpcenv->{type
} ne 'ha')) {
2044 my $service = "vm:$vmid";
2046 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2048 print "Requesting HA stop for VM $vmid\n";
2050 PVE
::Tools
::run_command
($cmd);
2055 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2062 syslog
('info', "shutdown VM $vmid: $upid\n");
2064 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2065 $shutdown, $param->{forceStop
}, $keepActive);
2070 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2074 __PACKAGE__-
>register_method({
2075 name
=> 'vm_suspend',
2076 path
=> '{vmid}/status/suspend',
2080 description
=> "Suspend virtual machine.",
2082 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2085 additionalProperties
=> 0,
2087 node
=> get_standard_option
('pve-node'),
2088 vmid
=> get_standard_option
('pve-vmid',
2089 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2090 skiplock
=> get_standard_option
('skiplock'),
2099 my $rpcenv = PVE
::RPCEnvironment
::get
();
2101 my $authuser = $rpcenv->get_user();
2103 my $node = extract_param
($param, 'node');
2105 my $vmid = extract_param
($param, 'vmid');
2107 my $skiplock = extract_param
($param, 'skiplock');
2108 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2109 if $skiplock && $authuser ne 'root@pam';
2111 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2116 syslog
('info', "suspend VM $vmid: $upid\n");
2118 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2123 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2126 __PACKAGE__-
>register_method({
2127 name
=> 'vm_resume',
2128 path
=> '{vmid}/status/resume',
2132 description
=> "Resume virtual machine.",
2134 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2137 additionalProperties
=> 0,
2139 node
=> get_standard_option
('pve-node'),
2140 vmid
=> get_standard_option
('pve-vmid',
2141 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2142 skiplock
=> get_standard_option
('skiplock'),
2143 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2153 my $rpcenv = PVE
::RPCEnvironment
::get
();
2155 my $authuser = $rpcenv->get_user();
2157 my $node = extract_param
($param, 'node');
2159 my $vmid = extract_param
($param, 'vmid');
2161 my $skiplock = extract_param
($param, 'skiplock');
2162 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2163 if $skiplock && $authuser ne 'root@pam';
2165 my $nocheck = extract_param
($param, 'nocheck');
2167 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2172 syslog
('info', "resume VM $vmid: $upid\n");
2174 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2179 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2182 __PACKAGE__-
>register_method({
2183 name
=> 'vm_sendkey',
2184 path
=> '{vmid}/sendkey',
2188 description
=> "Send key event to virtual machine.",
2190 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2193 additionalProperties
=> 0,
2195 node
=> get_standard_option
('pve-node'),
2196 vmid
=> get_standard_option
('pve-vmid',
2197 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2198 skiplock
=> get_standard_option
('skiplock'),
2200 description
=> "The key (qemu monitor encoding).",
2205 returns
=> { type
=> 'null'},
2209 my $rpcenv = PVE
::RPCEnvironment
::get
();
2211 my $authuser = $rpcenv->get_user();
2213 my $node = extract_param
($param, 'node');
2215 my $vmid = extract_param
($param, 'vmid');
2217 my $skiplock = extract_param
($param, 'skiplock');
2218 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2219 if $skiplock && $authuser ne 'root@pam';
2221 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2226 __PACKAGE__-
>register_method({
2227 name
=> 'vm_feature',
2228 path
=> '{vmid}/feature',
2232 description
=> "Check if feature for virtual machine is available.",
2234 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2237 additionalProperties
=> 0,
2239 node
=> get_standard_option
('pve-node'),
2240 vmid
=> get_standard_option
('pve-vmid'),
2242 description
=> "Feature to check.",
2244 enum
=> [ 'snapshot', 'clone', 'copy' ],
2246 snapname
=> get_standard_option
('pve-snapshot-name', {
2254 hasFeature
=> { type
=> 'boolean' },
2257 items
=> { type
=> 'string' },
2264 my $node = extract_param
($param, 'node');
2266 my $vmid = extract_param
($param, 'vmid');
2268 my $snapname = extract_param
($param, 'snapname');
2270 my $feature = extract_param
($param, 'feature');
2272 my $running = PVE
::QemuServer
::check_running
($vmid);
2274 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2277 my $snap = $conf->{snapshots
}->{$snapname};
2278 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2281 my $storecfg = PVE
::Storage
::config
();
2283 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2284 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2287 hasFeature
=> $hasFeature,
2288 nodes
=> [ keys %$nodelist ],
2292 __PACKAGE__-
>register_method({
2294 path
=> '{vmid}/clone',
2298 description
=> "Create a copy of virtual machine/template.",
2300 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2301 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2302 "'Datastore.AllocateSpace' on any used storage.",
2305 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2307 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2308 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2313 additionalProperties
=> 0,
2315 node
=> get_standard_option
('pve-node'),
2316 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2317 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2320 type
=> 'string', format
=> 'dns-name',
2321 description
=> "Set a name for the new VM.",
2326 description
=> "Description for the new VM.",
2330 type
=> 'string', format
=> 'pve-poolid',
2331 description
=> "Add the new VM to the specified pool.",
2333 snapname
=> get_standard_option
('pve-snapshot-name', {
2336 storage
=> get_standard_option
('pve-storage-id', {
2337 description
=> "Target storage for full clone.",
2342 description
=> "Target format for file storage.",
2346 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2351 description
=> "Create a full copy of all disk. This is always done when " .
2352 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2355 target
=> get_standard_option
('pve-node', {
2356 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2367 my $rpcenv = PVE
::RPCEnvironment
::get
();
2369 my $authuser = $rpcenv->get_user();
2371 my $node = extract_param
($param, 'node');
2373 my $vmid = extract_param
($param, 'vmid');
2375 my $newid = extract_param
($param, 'newid');
2377 my $pool = extract_param
($param, 'pool');
2379 if (defined($pool)) {
2380 $rpcenv->check_pool_exist($pool);
2383 my $snapname = extract_param
($param, 'snapname');
2385 my $storage = extract_param
($param, 'storage');
2387 my $format = extract_param
($param, 'format');
2389 my $target = extract_param
($param, 'target');
2391 my $localnode = PVE
::INotify
::nodename
();
2393 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2395 PVE
::Cluster
::check_node_exists
($target) if $target;
2397 my $storecfg = PVE
::Storage
::config
();
2400 # check if storage is enabled on local node
2401 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2403 # check if storage is available on target node
2404 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2405 # clone only works if target storage is shared
2406 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2407 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2411 PVE
::Cluster
::check_cfs_quorum
();
2413 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2415 # exclusive lock if VM is running - else shared lock is enough;
2416 my $shared_lock = $running ?
0 : 1;
2420 # do all tests after lock
2421 # we also try to do all tests before we fork the worker
2423 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2425 PVE
::QemuConfig-
>check_lock($conf);
2427 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2429 die "unexpected state change\n" if $verify_running != $running;
2431 die "snapshot '$snapname' does not exist\n"
2432 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2434 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2436 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2438 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2440 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2442 die "unable to create VM $newid: config file already exists\n"
2445 my $newconf = { lock => 'clone' };
2450 foreach my $opt (keys %$oldconf) {
2451 my $value = $oldconf->{$opt};
2453 # do not copy snapshot related info
2454 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2455 $opt eq 'vmstate' || $opt eq 'snapstate';
2457 # no need to copy unused images, because VMID(owner) changes anyways
2458 next if $opt =~ m/^unused\d+$/;
2460 # always change MAC! address
2461 if ($opt =~ m/^net(\d+)$/) {
2462 my $net = PVE
::QemuServer
::parse_net
($value);
2463 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2464 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2465 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2466 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2467 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2468 die "unable to parse drive options for '$opt'\n" if !$drive;
2469 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2470 $newconf->{$opt} = $value; # simply copy configuration
2472 if ($param->{full
}) {
2473 die "Full clone feature is not supported for drive '$opt'\n"
2474 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2475 $fullclone->{$opt} = 1;
2477 # not full means clone instead of copy
2478 die "Linked clone feature is not supported for drive '$opt'\n"
2479 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2481 $drives->{$opt} = $drive;
2482 push @$vollist, $drive->{file
};
2485 # copy everything else
2486 $newconf->{$opt} = $value;
2490 # auto generate a new uuid
2491 my ($uuid, $uuid_str);
2492 UUID
::generate
($uuid);
2493 UUID
::unparse
($uuid, $uuid_str);
2494 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2495 $smbios1->{uuid
} = $uuid_str;
2496 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2498 delete $newconf->{template
};
2500 if ($param->{name
}) {
2501 $newconf->{name
} = $param->{name
};
2503 if ($oldconf->{name
}) {
2504 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2506 $newconf->{name
} = "Copy-of-VM-$vmid";
2510 if ($param->{description
}) {
2511 $newconf->{description
} = $param->{description
};
2514 # create empty/temp config - this fails if VM already exists on other node
2515 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2520 my $newvollist = [];
2524 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2526 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2528 my $total_jobs = scalar(keys %{$drives});
2531 foreach my $opt (keys %$drives) {
2532 my $drive = $drives->{$opt};
2533 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2535 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2536 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2537 $jobs, $skipcomplete, $oldconf->{agent
});
2539 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2541 PVE
::QemuConfig-
>write_config($newid, $newconf);
2545 delete $newconf->{lock};
2546 PVE
::QemuConfig-
>write_config($newid, $newconf);
2549 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2550 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2551 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2553 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2554 die "Failed to move config to node '$target' - rename failed: $!\n"
2555 if !rename($conffile, $newconffile);
2558 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2563 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2565 sleep 1; # some storage like rbd need to wait before release volume - really?
2567 foreach my $volid (@$newvollist) {
2568 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2571 die "clone failed: $err";
2577 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2579 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2582 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2583 # Aquire exclusive lock lock for $newid
2584 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2589 __PACKAGE__-
>register_method({
2590 name
=> 'move_vm_disk',
2591 path
=> '{vmid}/move_disk',
2595 description
=> "Move volume to different storage.",
2597 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2599 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2600 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2604 additionalProperties
=> 0,
2606 node
=> get_standard_option
('pve-node'),
2607 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2610 description
=> "The disk you want to move.",
2611 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2613 storage
=> get_standard_option
('pve-storage-id', {
2614 description
=> "Target storage.",
2615 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2619 description
=> "Target Format.",
2620 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2625 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2631 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2639 description
=> "the task ID.",
2644 my $rpcenv = PVE
::RPCEnvironment
::get
();
2646 my $authuser = $rpcenv->get_user();
2648 my $node = extract_param
($param, 'node');
2650 my $vmid = extract_param
($param, 'vmid');
2652 my $digest = extract_param
($param, 'digest');
2654 my $disk = extract_param
($param, 'disk');
2656 my $storeid = extract_param
($param, 'storage');
2658 my $format = extract_param
($param, 'format');
2660 my $storecfg = PVE
::Storage
::config
();
2662 my $updatefn = sub {
2664 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2666 PVE
::QemuConfig-
>check_lock($conf);
2668 die "checksum missmatch (file change by other user?)\n"
2669 if $digest && $digest ne $conf->{digest
};
2671 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2673 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2675 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2677 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2680 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2681 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2685 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2686 (!$format || !$oldfmt || $oldfmt eq $format);
2688 # this only checks snapshots because $disk is passed!
2689 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2690 die "you can't move a disk with snapshots and delete the source\n"
2691 if $snapshotted && $param->{delete};
2693 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2695 my $running = PVE
::QemuServer
::check_running
($vmid);
2697 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2701 my $newvollist = [];
2704 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2706 warn "moving disk with snapshots, snapshots will not be moved!\n"
2709 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2710 $vmid, $storeid, $format, 1, $newvollist);
2712 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2714 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2716 # convert moved disk to base if part of template
2717 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2718 if PVE
::QemuConfig-
>is_template($conf);
2720 PVE
::QemuConfig-
>write_config($vmid, $conf);
2723 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2724 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2731 foreach my $volid (@$newvollist) {
2732 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2735 die "storage migration failed: $err";
2738 if ($param->{delete}) {
2740 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2741 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2747 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2750 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2753 __PACKAGE__-
>register_method({
2754 name
=> 'migrate_vm',
2755 path
=> '{vmid}/migrate',
2759 description
=> "Migrate virtual machine. Creates a new migration task.",
2761 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2764 additionalProperties
=> 0,
2766 node
=> get_standard_option
('pve-node'),
2767 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2768 target
=> get_standard_option
('pve-node', {
2769 description
=> "Target node.",
2770 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2774 description
=> "Use online/live migration.",
2779 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2784 enum
=> ['secure', 'insecure'],
2785 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2788 migration_network
=> {
2789 type
=> 'string', format
=> 'CIDR',
2790 description
=> "CIDR of the (sub) network that is used for migration.",
2793 "with-local-disks" => {
2795 description
=> "Enable live storage migration for local disk",
2798 targetstorage
=> get_standard_option
('pve-storage-id', {
2799 description
=> "Default target storage.",
2801 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2807 description
=> "the task ID.",
2812 my $rpcenv = PVE
::RPCEnvironment
::get
();
2814 my $authuser = $rpcenv->get_user();
2816 my $target = extract_param
($param, 'target');
2818 my $localnode = PVE
::INotify
::nodename
();
2819 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2821 PVE
::Cluster
::check_cfs_quorum
();
2823 PVE
::Cluster
::check_node_exists
($target);
2825 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2827 my $vmid = extract_param
($param, 'vmid');
2829 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2830 if !$param->{online
} && $param->{targetstorage
};
2832 raise_param_exc
({ force
=> "Only root may use this option." })
2833 if $param->{force
} && $authuser ne 'root@pam';
2835 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2836 if $param->{migration_type
} && $authuser ne 'root@pam';
2838 # allow root only until better network permissions are available
2839 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2840 if $param->{migration_network
} && $authuser ne 'root@pam';
2843 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2845 # try to detect errors early
2847 PVE
::QemuConfig-
>check_lock($conf);
2849 if (PVE
::QemuServer
::check_running
($vmid)) {
2850 die "cant migrate running VM without --online\n"
2851 if !$param->{online
};
2854 my $storecfg = PVE
::Storage
::config
();
2856 if( $param->{targetstorage
}) {
2857 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2859 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2862 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2867 my $service = "vm:$vmid";
2869 my $cmd = ['ha-manager', 'migrate', $service, $target];
2871 print "Requesting HA migration for VM $vmid to node $target\n";
2873 PVE
::Tools
::run_command
($cmd);
2878 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2883 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2887 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2890 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2895 __PACKAGE__-
>register_method({
2897 path
=> '{vmid}/monitor',
2901 description
=> "Execute Qemu monitor commands.",
2903 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2904 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2907 additionalProperties
=> 0,
2909 node
=> get_standard_option
('pve-node'),
2910 vmid
=> get_standard_option
('pve-vmid'),
2913 description
=> "The monitor command.",
2917 returns
=> { type
=> 'string'},
2921 my $rpcenv = PVE
::RPCEnvironment
::get
();
2922 my $authuser = $rpcenv->get_user();
2925 my $command = shift;
2926 return $command =~ m/^\s*info(\s+|$)/
2927 || $command =~ m/^\s*help\s*$/;
2930 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2931 if !&$is_ro($param->{command
});
2933 my $vmid = $param->{vmid
};
2935 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2939 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2941 $res = "ERROR: $@" if $@;
2946 my $guest_agent_commands = [
2954 'network-get-interfaces',
2957 'get-memory-blocks',
2958 'get-memory-block-info',
2965 __PACKAGE__-
>register_method({
2967 path
=> '{vmid}/agent',
2971 description
=> "Execute Qemu Guest Agent commands.",
2973 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2976 additionalProperties
=> 0,
2978 node
=> get_standard_option
('pve-node'),
2979 vmid
=> get_standard_option
('pve-vmid', {
2980 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2983 description
=> "The QGA command.",
2984 enum
=> $guest_agent_commands,
2990 description
=> "Returns an object with a single `result` property. The type of that
2991 property depends on the executed command.",
2996 my $vmid = $param->{vmid
};
2998 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3000 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3001 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3003 my $cmd = $param->{command
};
3005 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3007 return { result
=> $res };
3010 __PACKAGE__-
>register_method({
3011 name
=> 'resize_vm',
3012 path
=> '{vmid}/resize',
3016 description
=> "Extend volume size.",
3018 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3021 additionalProperties
=> 0,
3023 node
=> get_standard_option
('pve-node'),
3024 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3025 skiplock
=> get_standard_option
('skiplock'),
3028 description
=> "The disk you want to resize.",
3029 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3033 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3034 description
=> "The new size. With the `+` sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
3038 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3044 returns
=> { type
=> 'null'},
3048 my $rpcenv = PVE
::RPCEnvironment
::get
();
3050 my $authuser = $rpcenv->get_user();
3052 my $node = extract_param
($param, 'node');
3054 my $vmid = extract_param
($param, 'vmid');
3056 my $digest = extract_param
($param, 'digest');
3058 my $disk = extract_param
($param, 'disk');
3060 my $sizestr = extract_param
($param, 'size');
3062 my $skiplock = extract_param
($param, 'skiplock');
3063 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3064 if $skiplock && $authuser ne 'root@pam';
3066 my $storecfg = PVE
::Storage
::config
();
3068 my $updatefn = sub {
3070 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3072 die "checksum missmatch (file change by other user?)\n"
3073 if $digest && $digest ne $conf->{digest
};
3074 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3076 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3078 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3080 my (undef, undef, undef, undef, undef, undef, $format) =
3081 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3083 die "can't resize volume: $disk if snapshot exists\n"
3084 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3086 my $volid = $drive->{file
};
3088 die "disk '$disk' has no associated volume\n" if !$volid;
3090 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3092 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3094 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3096 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3097 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3099 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3100 my ($ext, $newsize, $unit) = ($1, $2, $4);
3103 $newsize = $newsize * 1024;
3104 } elsif ($unit eq 'M') {
3105 $newsize = $newsize * 1024 * 1024;
3106 } elsif ($unit eq 'G') {
3107 $newsize = $newsize * 1024 * 1024 * 1024;
3108 } elsif ($unit eq 'T') {
3109 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3112 $newsize += $size if $ext;
3113 $newsize = int($newsize);
3115 die "shrinking disks is not supported\n" if $newsize < $size;
3117 return if $size == $newsize;
3119 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3121 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3123 $drive->{size
} = $newsize;
3124 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3126 PVE
::QemuConfig-
>write_config($vmid, $conf);
3129 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3133 __PACKAGE__-
>register_method({
3134 name
=> 'snapshot_list',
3135 path
=> '{vmid}/snapshot',
3137 description
=> "List all snapshots.",
3139 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3142 protected
=> 1, # qemu pid files are only readable by root
3144 additionalProperties
=> 0,
3146 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3147 node
=> get_standard_option
('pve-node'),
3156 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3161 my $vmid = $param->{vmid
};
3163 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3164 my $snaphash = $conf->{snapshots
} || {};
3168 foreach my $name (keys %$snaphash) {
3169 my $d = $snaphash->{$name};
3172 snaptime
=> $d->{snaptime
} || 0,
3173 vmstate
=> $d->{vmstate
} ?
1 : 0,
3174 description
=> $d->{description
} || '',
3176 $item->{parent
} = $d->{parent
} if $d->{parent
};
3177 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3181 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3182 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3183 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3185 push @$res, $current;
3190 __PACKAGE__-
>register_method({
3192 path
=> '{vmid}/snapshot',
3196 description
=> "Snapshot a VM.",
3198 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3201 additionalProperties
=> 0,
3203 node
=> get_standard_option
('pve-node'),
3204 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3205 snapname
=> get_standard_option
('pve-snapshot-name'),
3209 description
=> "Save the vmstate",
3214 description
=> "A textual description or comment.",
3220 description
=> "the task ID.",
3225 my $rpcenv = PVE
::RPCEnvironment
::get
();
3227 my $authuser = $rpcenv->get_user();
3229 my $node = extract_param
($param, 'node');
3231 my $vmid = extract_param
($param, 'vmid');
3233 my $snapname = extract_param
($param, 'snapname');
3235 die "unable to use snapshot name 'current' (reserved name)\n"
3236 if $snapname eq 'current';
3239 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3240 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3241 $param->{description
});
3244 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3247 __PACKAGE__-
>register_method({
3248 name
=> 'snapshot_cmd_idx',
3249 path
=> '{vmid}/snapshot/{snapname}',
3256 additionalProperties
=> 0,
3258 vmid
=> get_standard_option
('pve-vmid'),
3259 node
=> get_standard_option
('pve-node'),
3260 snapname
=> get_standard_option
('pve-snapshot-name'),
3269 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3276 push @$res, { cmd
=> 'rollback' };
3277 push @$res, { cmd
=> 'config' };
3282 __PACKAGE__-
>register_method({
3283 name
=> 'update_snapshot_config',
3284 path
=> '{vmid}/snapshot/{snapname}/config',
3288 description
=> "Update snapshot metadata.",
3290 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3293 additionalProperties
=> 0,
3295 node
=> get_standard_option
('pve-node'),
3296 vmid
=> get_standard_option
('pve-vmid'),
3297 snapname
=> get_standard_option
('pve-snapshot-name'),
3301 description
=> "A textual description or comment.",
3305 returns
=> { type
=> 'null' },
3309 my $rpcenv = PVE
::RPCEnvironment
::get
();
3311 my $authuser = $rpcenv->get_user();
3313 my $vmid = extract_param
($param, 'vmid');
3315 my $snapname = extract_param
($param, 'snapname');
3317 return undef if !defined($param->{description
});
3319 my $updatefn = sub {
3321 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3323 PVE
::QemuConfig-
>check_lock($conf);
3325 my $snap = $conf->{snapshots
}->{$snapname};
3327 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3329 $snap->{description
} = $param->{description
} if defined($param->{description
});
3331 PVE
::QemuConfig-
>write_config($vmid, $conf);
3334 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3339 __PACKAGE__-
>register_method({
3340 name
=> 'get_snapshot_config',
3341 path
=> '{vmid}/snapshot/{snapname}/config',
3344 description
=> "Get snapshot configuration",
3346 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3349 additionalProperties
=> 0,
3351 node
=> get_standard_option
('pve-node'),
3352 vmid
=> get_standard_option
('pve-vmid'),
3353 snapname
=> get_standard_option
('pve-snapshot-name'),
3356 returns
=> { type
=> "object" },
3360 my $rpcenv = PVE
::RPCEnvironment
::get
();
3362 my $authuser = $rpcenv->get_user();
3364 my $vmid = extract_param
($param, 'vmid');
3366 my $snapname = extract_param
($param, 'snapname');
3368 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3370 my $snap = $conf->{snapshots
}->{$snapname};
3372 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3377 __PACKAGE__-
>register_method({
3379 path
=> '{vmid}/snapshot/{snapname}/rollback',
3383 description
=> "Rollback VM state to specified snapshot.",
3385 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3388 additionalProperties
=> 0,
3390 node
=> get_standard_option
('pve-node'),
3391 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3392 snapname
=> get_standard_option
('pve-snapshot-name'),
3397 description
=> "the task ID.",
3402 my $rpcenv = PVE
::RPCEnvironment
::get
();
3404 my $authuser = $rpcenv->get_user();
3406 my $node = extract_param
($param, 'node');
3408 my $vmid = extract_param
($param, 'vmid');
3410 my $snapname = extract_param
($param, 'snapname');
3413 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3414 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3418 # hold migration lock, this makes sure that nobody create replication snapshots
3419 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3422 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3425 __PACKAGE__-
>register_method({
3426 name
=> 'delsnapshot',
3427 path
=> '{vmid}/snapshot/{snapname}',
3431 description
=> "Delete a VM snapshot.",
3433 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3436 additionalProperties
=> 0,
3438 node
=> get_standard_option
('pve-node'),
3439 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3440 snapname
=> get_standard_option
('pve-snapshot-name'),
3444 description
=> "For removal from config file, even if removing disk snapshots fails.",
3450 description
=> "the task ID.",
3455 my $rpcenv = PVE
::RPCEnvironment
::get
();
3457 my $authuser = $rpcenv->get_user();
3459 my $node = extract_param
($param, 'node');
3461 my $vmid = extract_param
($param, 'vmid');
3463 my $snapname = extract_param
($param, 'snapname');
3466 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3467 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3470 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3473 __PACKAGE__-
>register_method({
3475 path
=> '{vmid}/template',
3479 description
=> "Create a Template.",
3481 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3482 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3485 additionalProperties
=> 0,
3487 node
=> get_standard_option
('pve-node'),
3488 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3492 description
=> "If you want to convert only 1 disk to base image.",
3493 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3498 returns
=> { type
=> 'null'},
3502 my $rpcenv = PVE
::RPCEnvironment
::get
();
3504 my $authuser = $rpcenv->get_user();
3506 my $node = extract_param
($param, 'node');
3508 my $vmid = extract_param
($param, 'vmid');
3510 my $disk = extract_param
($param, 'disk');
3512 my $updatefn = sub {
3514 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3516 PVE
::QemuConfig-
>check_lock($conf);
3518 die "unable to create template, because VM contains snapshots\n"
3519 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3521 die "you can't convert a template to a template\n"
3522 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3524 die "you can't convert a VM to template if VM is running\n"
3525 if PVE
::QemuServer
::check_running
($vmid);
3528 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3531 $conf->{template
} = 1;
3532 PVE
::QemuConfig-
>write_config($vmid, $conf);
3534 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3537 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);