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 $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
56 my $check_storage_access = sub {
57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
59 PVE
::QemuServer
::foreach_drive
($settings, sub {
60 my ($ds, $drive) = @_;
62 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
64 my $volid = $drive->{file
};
66 if (!$volid || $volid eq 'none') {
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
70 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
74 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
75 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
76 if !$scfg->{content
}->{images
};
78 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
83 my $check_storage_access_clone = sub {
84 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
88 PVE
::QemuServer
::foreach_drive
($conf, sub {
89 my ($ds, $drive) = @_;
91 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
93 my $volid = $drive->{file
};
95 return if !$volid || $volid eq 'none';
98 if ($volid eq 'cdrom') {
99 $rpcenv->check($authuser, "/", ['Sys.Console']);
101 # we simply allow access
102 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
103 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
104 $sharedvm = 0 if !$scfg->{shared
};
108 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
109 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
110 $sharedvm = 0 if !$scfg->{shared
};
112 $sid = $storage if $storage;
113 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
120 # Note: $pool is only needed when creating a VM, because pool permissions
121 # are automatically inherited if VM already exists inside a pool.
122 my $create_disks = sub {
123 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
130 my ($ds, $disk) = @_;
132 my $volid = $disk->{file
};
134 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
135 delete $disk->{size
};
136 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
137 } elsif ($volid =~ $NEW_DISK_RE) {
138 my ($storeid, $size) = ($2 || $default_storage, $3);
139 die "no storage ID specified (and no default storage)\n" if !$storeid;
140 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
141 my $fmt = $disk->{format
} || $defformat;
144 if ($ds eq 'efidisk0') {
146 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
147 die "uefi vars image not found\n" if ! -f
$ovmfvars;
148 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
150 $disk->{file
} = $volid;
151 $disk->{size
} = 128*1024;
152 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
153 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
154 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
155 my $path = PVE
::Storage
::path
($storecfg, $volid);
156 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
157 push @$efidiskcmd, $ovmfvars;
158 push @$efidiskcmd, $path;
160 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
162 eval { PVE
::Tools
::run_command
($efidiskcmd); };
164 die "Copying of EFI Vars image failed: $err" if $err;
166 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
167 $fmt, undef, $size*1024*1024);
168 $disk->{file
} = $volid;
169 $disk->{size
} = $size*1024*1024*1024;
171 push @$vollist, $volid;
172 delete $disk->{format
}; # no longer needed
173 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
176 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
178 my $volid_is_new = 1;
181 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
182 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
187 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
189 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
191 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
193 die "volume $volid does not exists\n" if !$size;
195 $disk->{size
} = $size;
198 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
202 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
204 # free allocated images on error
206 syslog
('err', "VM $vmid creating disks failed");
207 foreach my $volid (@$vollist) {
208 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
214 # modify vm config if everything went well
215 foreach my $ds (keys %$res) {
216 $conf->{$ds} = $res->{$ds};
233 my $memoryoptions = {
239 my $hwtypeoptions = {
251 my $generaloptions = {
258 'migrate_downtime' => 1,
259 'migrate_speed' => 1,
271 my $vmpoweroptions = {
280 my $check_vm_modify_config_perm = sub {
281 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
283 return 1 if $authuser eq 'root@pam';
285 foreach my $opt (@$key_list) {
286 # disk checks need to be done somewhere else
287 next if PVE
::QemuServer
::is_valid_drivename
($opt);
288 next if $opt eq 'cdrom';
289 next if $opt =~ m/^unused\d+$/;
291 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
292 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
293 } elsif ($memoryoptions->{$opt}) {
294 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
295 } elsif ($hwtypeoptions->{$opt}) {
296 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
297 } elsif ($generaloptions->{$opt}) {
298 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
299 # special case for startup since it changes host behaviour
300 if ($opt eq 'startup') {
301 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
303 } elsif ($vmpoweroptions->{$opt}) {
304 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
305 } elsif ($diskoptions->{$opt}) {
306 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
307 } elsif ($opt =~ m/^net\d+$/) {
308 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
310 # catches usb\d+, hostpci\d+, args, lock, etc.
311 # new options will be checked here
312 die "only root can set '$opt' config\n";
319 __PACKAGE__-
>register_method({
323 description
=> "Virtual machine index (per node).",
325 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
329 protected
=> 1, # qemu pid files are only readable by root
331 additionalProperties
=> 0,
333 node
=> get_standard_option
('pve-node'),
337 description
=> "Determine the full status of active VMs.",
347 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
352 my $rpcenv = PVE
::RPCEnvironment
::get
();
353 my $authuser = $rpcenv->get_user();
355 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
358 foreach my $vmid (keys %$vmstatus) {
359 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
361 my $data = $vmstatus->{$vmid};
362 $data->{vmid
} = int($vmid);
371 __PACKAGE__-
>register_method({
375 description
=> "Create or restore a virtual machine.",
377 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
378 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
379 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
380 user
=> 'all', # check inside
385 additionalProperties
=> 0,
386 properties
=> PVE
::QemuServer
::json_config_properties
(
388 node
=> get_standard_option
('pve-node'),
389 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
391 description
=> "The backup file.",
395 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
397 storage
=> get_standard_option
('pve-storage-id', {
398 description
=> "Default storage.",
400 completion
=> \
&PVE
::QemuServer
::complete_storage
,
405 description
=> "Allow to overwrite existing VM.",
406 requires
=> 'archive',
411 description
=> "Assign a unique random ethernet address.",
412 requires
=> 'archive',
416 type
=> 'string', format
=> 'pve-poolid',
417 description
=> "Add the VM to the specified pool.",
427 my $rpcenv = PVE
::RPCEnvironment
::get
();
429 my $authuser = $rpcenv->get_user();
431 my $node = extract_param
($param, 'node');
433 my $vmid = extract_param
($param, 'vmid');
435 my $archive = extract_param
($param, 'archive');
437 my $storage = extract_param
($param, 'storage');
439 my $force = extract_param
($param, 'force');
441 my $unique = extract_param
($param, 'unique');
443 my $pool = extract_param
($param, 'pool');
445 my $filename = PVE
::QemuConfig-
>config_file($vmid);
447 my $storecfg = PVE
::Storage
::config
();
449 PVE
::Cluster
::check_cfs_quorum
();
451 if (defined($pool)) {
452 $rpcenv->check_pool_exist($pool);
455 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
456 if defined($storage);
458 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
460 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
462 } elsif ($archive && $force && (-f
$filename) &&
463 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
464 # OK: user has VM.Backup permissions, and want to restore an existing VM
470 &$resolve_cdrom_alias($param);
472 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
474 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
476 foreach my $opt (keys %$param) {
477 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
478 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
479 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
481 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
482 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
486 PVE
::QemuServer
::add_random_macs
($param);
488 my $keystr = join(' ', keys %$param);
489 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
491 if ($archive eq '-') {
492 die "pipe requires cli environment\n"
493 if $rpcenv->{type
} ne 'cli';
495 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
496 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
500 my $restorefn = sub {
501 my $vmlist = PVE
::Cluster
::get_vmlist
();
502 if ($vmlist->{ids
}->{$vmid}) {
503 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
504 if ($current_node eq $node) {
505 my $conf = PVE
::QemuConfig-
>load_config($vmid);
507 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
509 die "unable to restore vm $vmid - config file already exists\n"
512 die "unable to restore vm $vmid - vm is running\n"
513 if PVE
::QemuServer
::check_running
($vmid);
515 die "unable to restore vm $vmid - vm is a template\n"
516 if PVE
::QemuConfig-
>is_template($conf);
519 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
524 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
527 unique
=> $unique });
529 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
532 # ensure no old replication state are exists
533 PVE
::ReplicationState
::delete_guest_states
($vmid);
535 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
541 PVE
::Cluster
::check_vmid_unused
($vmid);
543 # ensure no old replication state are exists
544 PVE
::ReplicationState
::delete_guest_states
($vmid);
554 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
556 # try to be smart about bootdisk
557 my @disks = PVE
::QemuServer
::valid_drive_names
();
559 foreach my $ds (reverse @disks) {
560 next if !$conf->{$ds};
561 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
562 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
566 if (!$conf->{bootdisk
} && $firstdisk) {
567 $conf->{bootdisk
} = $firstdisk;
570 # auto generate uuid if user did not specify smbios1 option
571 if (!$conf->{smbios1
}) {
572 my ($uuid, $uuid_str);
573 UUID
::generate
($uuid);
574 UUID
::unparse
($uuid, $uuid_str);
575 $conf->{smbios1
} = "uuid=$uuid_str";
578 PVE
::QemuConfig-
>write_config($vmid, $conf);
584 foreach my $volid (@$vollist) {
585 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
588 die "create failed - $err";
591 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
594 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
597 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
600 __PACKAGE__-
>register_method({
605 description
=> "Directory index",
610 additionalProperties
=> 0,
612 node
=> get_standard_option
('pve-node'),
613 vmid
=> get_standard_option
('pve-vmid'),
621 subdir
=> { type
=> 'string' },
624 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
630 { subdir
=> 'config' },
631 { subdir
=> 'pending' },
632 { subdir
=> 'status' },
633 { subdir
=> 'unlink' },
634 { subdir
=> 'vncproxy' },
635 { subdir
=> 'migrate' },
636 { subdir
=> 'resize' },
637 { subdir
=> 'move' },
639 { subdir
=> 'rrddata' },
640 { subdir
=> 'monitor' },
641 { subdir
=> 'agent' },
642 { subdir
=> 'snapshot' },
643 { subdir
=> 'spiceproxy' },
644 { subdir
=> 'sendkey' },
645 { subdir
=> 'firewall' },
651 __PACKAGE__-
>register_method ({
652 subclass
=> "PVE::API2::Firewall::VM",
653 path
=> '{vmid}/firewall',
656 __PACKAGE__-
>register_method({
658 path
=> '{vmid}/rrd',
660 protected
=> 1, # fixme: can we avoid that?
662 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
664 description
=> "Read VM RRD statistics (returns PNG)",
666 additionalProperties
=> 0,
668 node
=> get_standard_option
('pve-node'),
669 vmid
=> get_standard_option
('pve-vmid'),
671 description
=> "Specify the time frame you are interested in.",
673 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
676 description
=> "The list of datasources you want to display.",
677 type
=> 'string', format
=> 'pve-configid-list',
680 description
=> "The RRD consolidation function",
682 enum
=> [ 'AVERAGE', 'MAX' ],
690 filename
=> { type
=> 'string' },
696 return PVE
::Cluster
::create_rrd_graph
(
697 "pve2-vm/$param->{vmid}", $param->{timeframe
},
698 $param->{ds
}, $param->{cf
});
702 __PACKAGE__-
>register_method({
704 path
=> '{vmid}/rrddata',
706 protected
=> 1, # fixme: can we avoid that?
708 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
710 description
=> "Read VM RRD statistics",
712 additionalProperties
=> 0,
714 node
=> get_standard_option
('pve-node'),
715 vmid
=> get_standard_option
('pve-vmid'),
717 description
=> "Specify the time frame you are interested in.",
719 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
722 description
=> "The RRD consolidation function",
724 enum
=> [ 'AVERAGE', 'MAX' ],
739 return PVE
::Cluster
::create_rrd_data
(
740 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
744 __PACKAGE__-
>register_method({
746 path
=> '{vmid}/config',
749 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
751 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
754 additionalProperties
=> 0,
756 node
=> get_standard_option
('pve-node'),
757 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
759 description
=> "Get current values (instead of pending values).",
771 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
778 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
780 delete $conf->{snapshots
};
782 if (!$param->{current
}) {
783 foreach my $opt (keys %{$conf->{pending
}}) {
784 next if $opt eq 'delete';
785 my $value = $conf->{pending
}->{$opt};
786 next if ref($value); # just to be sure
787 $conf->{$opt} = $value;
789 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
790 foreach my $opt (keys %$pending_delete_hash) {
791 delete $conf->{$opt} if $conf->{$opt};
795 delete $conf->{pending
};
800 __PACKAGE__-
>register_method({
801 name
=> 'vm_pending',
802 path
=> '{vmid}/pending',
805 description
=> "Get virtual machine configuration, including pending changes.",
807 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
810 additionalProperties
=> 0,
812 node
=> get_standard_option
('pve-node'),
813 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
822 description
=> "Configuration option name.",
826 description
=> "Current value.",
831 description
=> "Pending value.",
836 description
=> "Indicates a pending delete request if present and not 0. " .
837 "The value 2 indicates a force-delete request.",
849 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
851 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
855 foreach my $opt (keys %$conf) {
856 next if ref($conf->{$opt});
857 my $item = { key
=> $opt };
858 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
859 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
860 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
864 foreach my $opt (keys %{$conf->{pending
}}) {
865 next if $opt eq 'delete';
866 next if ref($conf->{pending
}->{$opt}); # just to be sure
867 next if defined($conf->{$opt});
868 my $item = { key
=> $opt };
869 $item->{pending
} = $conf->{pending
}->{$opt};
873 while (my ($opt, $force) = each %$pending_delete_hash) {
874 next if $conf->{pending
}->{$opt}; # just to be sure
875 next if $conf->{$opt};
876 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
883 # POST/PUT {vmid}/config implementation
885 # The original API used PUT (idempotent) an we assumed that all operations
886 # are fast. But it turned out that almost any configuration change can
887 # involve hot-plug actions, or disk alloc/free. Such actions can take long
888 # time to complete and have side effects (not idempotent).
890 # The new implementation uses POST and forks a worker process. We added
891 # a new option 'background_delay'. If specified we wait up to
892 # 'background_delay' second for the worker task to complete. It returns null
893 # if the task is finished within that time, else we return the UPID.
895 my $update_vm_api = sub {
896 my ($param, $sync) = @_;
898 my $rpcenv = PVE
::RPCEnvironment
::get
();
900 my $authuser = $rpcenv->get_user();
902 my $node = extract_param
($param, 'node');
904 my $vmid = extract_param
($param, 'vmid');
906 my $digest = extract_param
($param, 'digest');
908 my $background_delay = extract_param
($param, 'background_delay');
910 my @paramarr = (); # used for log message
911 foreach my $key (sort keys %$param) {
912 push @paramarr, "-$key", $param->{$key};
915 my $skiplock = extract_param
($param, 'skiplock');
916 raise_param_exc
({ skiplock
=> "Only root may use this option." })
917 if $skiplock && $authuser ne 'root@pam';
919 my $delete_str = extract_param
($param, 'delete');
921 my $revert_str = extract_param
($param, 'revert');
923 my $force = extract_param
($param, 'force');
925 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
927 my $storecfg = PVE
::Storage
::config
();
929 my $defaults = PVE
::QemuServer
::load_defaults
();
931 &$resolve_cdrom_alias($param);
933 # now try to verify all parameters
936 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
937 if (!PVE
::QemuServer
::option_exists
($opt)) {
938 raise_param_exc
({ revert
=> "unknown option '$opt'" });
941 raise_param_exc
({ delete => "you can't use '-$opt' and " .
942 "-revert $opt' at the same time" })
943 if defined($param->{$opt});
949 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
950 $opt = 'ide2' if $opt eq 'cdrom';
952 raise_param_exc
({ delete => "you can't use '-$opt' and " .
953 "-delete $opt' at the same time" })
954 if defined($param->{$opt});
956 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
957 "-revert $opt' at the same time" })
960 if (!PVE
::QemuServer
::option_exists
($opt)) {
961 raise_param_exc
({ delete => "unknown option '$opt'" });
967 my $repl_conf = PVE
::ReplicationConfig-
>new();
968 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
969 my $check_replication = sub {
971 return if !$is_replicated;
972 my $volid = $drive->{file
};
973 return if !$volid || !($drive->{replicate
}//1);
974 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
975 my ($storeid, $format);
976 if ($volid =~ $NEW_DISK_RE) {
978 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
980 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
981 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
983 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
984 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
985 return if $scfg->{shared
};
986 die "cannot add non-replicatable volume to a replicated VM\n";
989 foreach my $opt (keys %$param) {
990 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
992 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
993 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
994 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
995 $check_replication->($drive);
996 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
997 } elsif ($opt =~ m/^net(\d+)$/) {
999 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1000 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1004 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1006 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1008 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1010 my $updatefn = sub {
1012 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1014 die "checksum missmatch (file change by other user?)\n"
1015 if $digest && $digest ne $conf->{digest
};
1017 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1019 foreach my $opt (keys %$revert) {
1020 if (defined($conf->{$opt})) {
1021 $param->{$opt} = $conf->{$opt};
1022 } elsif (defined($conf->{pending
}->{$opt})) {
1027 if ($param->{memory
} || defined($param->{balloon
})) {
1028 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1029 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1031 die "balloon value too large (must be smaller than assigned memory)\n"
1032 if $balloon && $balloon > $maxmem;
1035 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1039 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1041 # write updates to pending section
1043 my $modified = {}; # record what $option we modify
1045 foreach my $opt (@delete) {
1046 $modified->{$opt} = 1;
1047 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1048 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1049 warn "cannot delete '$opt' - not set in current configuration!\n";
1050 $modified->{$opt} = 0;
1054 if ($opt =~ m/^unused/) {
1055 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1056 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1057 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1058 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1059 delete $conf->{$opt};
1060 PVE
::QemuConfig-
>write_config($vmid, $conf);
1062 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1063 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1064 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1065 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1066 if defined($conf->{pending
}->{$opt});
1067 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1068 PVE
::QemuConfig-
>write_config($vmid, $conf);
1070 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1071 PVE
::QemuConfig-
>write_config($vmid, $conf);
1075 foreach my $opt (keys %$param) { # add/change
1076 $modified->{$opt} = 1;
1077 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1078 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1080 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1081 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1082 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1083 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1085 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1087 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1088 if defined($conf->{pending
}->{$opt});
1090 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1092 $conf->{pending
}->{$opt} = $param->{$opt};
1094 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1095 PVE
::QemuConfig-
>write_config($vmid, $conf);
1098 # remove pending changes when nothing changed
1099 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1100 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1101 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1103 return if !scalar(keys %{$conf->{pending
}});
1105 my $running = PVE
::QemuServer
::check_running
($vmid);
1107 # apply pending changes
1109 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1113 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1114 raise_param_exc
($errors) if scalar(keys %$errors);
1116 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1126 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1128 if ($background_delay) {
1130 # Note: It would be better to do that in the Event based HTTPServer
1131 # to avoid blocking call to sleep.
1133 my $end_time = time() + $background_delay;
1135 my $task = PVE
::Tools
::upid_decode
($upid);
1138 while (time() < $end_time) {
1139 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1141 sleep(1); # this gets interrupted when child process ends
1145 my $status = PVE
::Tools
::upid_read_status
($upid);
1146 return undef if $status eq 'OK';
1155 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1158 my $vm_config_perm_list = [
1163 'VM.Config.Network',
1165 'VM.Config.Options',
1168 __PACKAGE__-
>register_method({
1169 name
=> 'update_vm_async',
1170 path
=> '{vmid}/config',
1174 description
=> "Set virtual machine options (asynchrounous API).",
1176 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1179 additionalProperties
=> 0,
1180 properties
=> PVE
::QemuServer
::json_config_properties
(
1182 node
=> get_standard_option
('pve-node'),
1183 vmid
=> get_standard_option
('pve-vmid'),
1184 skiplock
=> get_standard_option
('skiplock'),
1186 type
=> 'string', format
=> 'pve-configid-list',
1187 description
=> "A list of settings you want to delete.",
1191 type
=> 'string', format
=> 'pve-configid-list',
1192 description
=> "Revert a pending change.",
1197 description
=> $opt_force_description,
1199 requires
=> 'delete',
1203 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1207 background_delay
=> {
1209 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1220 code
=> $update_vm_api,
1223 __PACKAGE__-
>register_method({
1224 name
=> 'update_vm',
1225 path
=> '{vmid}/config',
1229 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1231 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1234 additionalProperties
=> 0,
1235 properties
=> PVE
::QemuServer
::json_config_properties
(
1237 node
=> get_standard_option
('pve-node'),
1238 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1239 skiplock
=> get_standard_option
('skiplock'),
1241 type
=> 'string', format
=> 'pve-configid-list',
1242 description
=> "A list of settings you want to delete.",
1246 type
=> 'string', format
=> 'pve-configid-list',
1247 description
=> "Revert a pending change.",
1252 description
=> $opt_force_description,
1254 requires
=> 'delete',
1258 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1264 returns
=> { type
=> 'null' },
1267 &$update_vm_api($param, 1);
1273 __PACKAGE__-
>register_method({
1274 name
=> 'destroy_vm',
1279 description
=> "Destroy the vm (also delete all used/owned volumes).",
1281 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1284 additionalProperties
=> 0,
1286 node
=> get_standard_option
('pve-node'),
1287 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1288 skiplock
=> get_standard_option
('skiplock'),
1297 my $rpcenv = PVE
::RPCEnvironment
::get
();
1299 my $authuser = $rpcenv->get_user();
1301 my $vmid = $param->{vmid
};
1303 my $skiplock = $param->{skiplock
};
1304 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1305 if $skiplock && $authuser ne 'root@pam';
1308 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1310 my $storecfg = PVE
::Storage
::config
();
1312 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1314 die "unable to remove VM $vmid - used in HA resources\n"
1315 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1317 # do not allow destroy if there are replication jobs
1318 my $repl_conf = PVE
::ReplicationConfig-
>new();
1319 $repl_conf->check_for_existing_jobs($vmid);
1321 # early tests (repeat after locking)
1322 die "VM $vmid is running - destroy failed\n"
1323 if PVE
::QemuServer
::check_running
($vmid);
1328 syslog
('info', "destroy VM $vmid: $upid\n");
1330 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1332 PVE
::AccessControl
::remove_vm_access
($vmid);
1334 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1337 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1340 __PACKAGE__-
>register_method({
1342 path
=> '{vmid}/unlink',
1346 description
=> "Unlink/delete disk images.",
1348 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1351 additionalProperties
=> 0,
1353 node
=> get_standard_option
('pve-node'),
1354 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1356 type
=> 'string', format
=> 'pve-configid-list',
1357 description
=> "A list of disk IDs you want to delete.",
1361 description
=> $opt_force_description,
1366 returns
=> { type
=> 'null'},
1370 $param->{delete} = extract_param
($param, 'idlist');
1372 __PACKAGE__-
>update_vm($param);
1379 __PACKAGE__-
>register_method({
1381 path
=> '{vmid}/vncproxy',
1385 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1387 description
=> "Creates a TCP VNC proxy connections.",
1389 additionalProperties
=> 0,
1391 node
=> get_standard_option
('pve-node'),
1392 vmid
=> get_standard_option
('pve-vmid'),
1396 description
=> "starts websockify instead of vncproxy",
1401 additionalProperties
=> 0,
1403 user
=> { type
=> 'string' },
1404 ticket
=> { type
=> 'string' },
1405 cert
=> { type
=> 'string' },
1406 port
=> { type
=> 'integer' },
1407 upid
=> { type
=> 'string' },
1413 my $rpcenv = PVE
::RPCEnvironment
::get
();
1415 my $authuser = $rpcenv->get_user();
1417 my $vmid = $param->{vmid
};
1418 my $node = $param->{node
};
1419 my $websocket = $param->{websocket
};
1421 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1423 my $authpath = "/vms/$vmid";
1425 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1427 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1430 my ($remip, $family);
1433 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1434 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1435 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1436 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1438 $family = PVE
::Tools
::get_host_address_family
($node);
1441 my $port = PVE
::Tools
::next_vnc_port
($family);
1448 syslog
('info', "starting vnc proxy $upid\n");
1452 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1454 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1456 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1457 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1458 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1459 '-timeout', $timeout, '-authpath', $authpath,
1460 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1461 PVE
::Tools
::run_command
($cmd);
1464 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1466 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1468 my $sock = IO
::Socket
::IP-
>new(
1473 GetAddrInfoFlags
=> 0,
1474 ) or die "failed to create socket: $!\n";
1475 # Inside the worker we shouldn't have any previous alarms
1476 # running anyway...:
1478 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1480 accept(my $cli, $sock) or die "connection failed: $!\n";
1483 if (PVE
::Tools
::run_command
($cmd,
1484 output
=> '>&'.fileno($cli),
1485 input
=> '<&'.fileno($cli),
1488 die "Failed to run vncproxy.\n";
1495 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1497 PVE
::Tools
::wait_for_vnc_port
($port);
1508 __PACKAGE__-
>register_method({
1509 name
=> 'vncwebsocket',
1510 path
=> '{vmid}/vncwebsocket',
1513 description
=> "You also need to pass a valid ticket (vncticket).",
1514 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1516 description
=> "Opens a weksocket for VNC traffic.",
1518 additionalProperties
=> 0,
1520 node
=> get_standard_option
('pve-node'),
1521 vmid
=> get_standard_option
('pve-vmid'),
1523 description
=> "Ticket from previous call to vncproxy.",
1528 description
=> "Port number returned by previous vncproxy call.",
1538 port
=> { type
=> 'string' },
1544 my $rpcenv = PVE
::RPCEnvironment
::get
();
1546 my $authuser = $rpcenv->get_user();
1548 my $vmid = $param->{vmid
};
1549 my $node = $param->{node
};
1551 my $authpath = "/vms/$vmid";
1553 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1555 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1557 # Note: VNC ports are acessible from outside, so we do not gain any
1558 # security if we verify that $param->{port} belongs to VM $vmid. This
1559 # check is done by verifying the VNC ticket (inside VNC protocol).
1561 my $port = $param->{port
};
1563 return { port
=> $port };
1566 __PACKAGE__-
>register_method({
1567 name
=> 'spiceproxy',
1568 path
=> '{vmid}/spiceproxy',
1573 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1575 description
=> "Returns a SPICE configuration to connect to the VM.",
1577 additionalProperties
=> 0,
1579 node
=> get_standard_option
('pve-node'),
1580 vmid
=> get_standard_option
('pve-vmid'),
1581 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1584 returns
=> get_standard_option
('remote-viewer-config'),
1588 my $rpcenv = PVE
::RPCEnvironment
::get
();
1590 my $authuser = $rpcenv->get_user();
1592 my $vmid = $param->{vmid
};
1593 my $node = $param->{node
};
1594 my $proxy = $param->{proxy
};
1596 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1597 my $title = "VM $vmid";
1598 $title .= " - ". $conf->{name
} if $conf->{name
};
1600 my $port = PVE
::QemuServer
::spice_port
($vmid);
1602 my ($ticket, undef, $remote_viewer_config) =
1603 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1605 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1606 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1608 return $remote_viewer_config;
1611 __PACKAGE__-
>register_method({
1613 path
=> '{vmid}/status',
1616 description
=> "Directory index",
1621 additionalProperties
=> 0,
1623 node
=> get_standard_option
('pve-node'),
1624 vmid
=> get_standard_option
('pve-vmid'),
1632 subdir
=> { type
=> 'string' },
1635 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1641 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1644 { subdir
=> 'current' },
1645 { subdir
=> 'start' },
1646 { subdir
=> 'stop' },
1652 __PACKAGE__-
>register_method({
1653 name
=> 'vm_status',
1654 path
=> '{vmid}/status/current',
1657 protected
=> 1, # qemu pid files are only readable by root
1658 description
=> "Get virtual machine status.",
1660 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1663 additionalProperties
=> 0,
1665 node
=> get_standard_option
('pve-node'),
1666 vmid
=> get_standard_option
('pve-vmid'),
1669 returns
=> { type
=> 'object' },
1674 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1676 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1677 my $status = $vmstatus->{$param->{vmid
}};
1679 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1681 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1686 __PACKAGE__-
>register_method({
1688 path
=> '{vmid}/status/start',
1692 description
=> "Start virtual machine.",
1694 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1697 additionalProperties
=> 0,
1699 node
=> get_standard_option
('pve-node'),
1700 vmid
=> get_standard_option
('pve-vmid',
1701 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1702 skiplock
=> get_standard_option
('skiplock'),
1703 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1704 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1707 enum
=> ['secure', 'insecure'],
1708 description
=> "Migration traffic is encrypted using an SSH " .
1709 "tunnel by default. On secure, completely private networks " .
1710 "this can be disabled to increase performance.",
1713 migration_network
=> {
1714 type
=> 'string', format
=> 'CIDR',
1715 description
=> "CIDR of the (sub) network that is used for migration.",
1718 machine
=> get_standard_option
('pve-qm-machine'),
1720 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1732 my $rpcenv = PVE
::RPCEnvironment
::get
();
1734 my $authuser = $rpcenv->get_user();
1736 my $node = extract_param
($param, 'node');
1738 my $vmid = extract_param
($param, 'vmid');
1740 my $machine = extract_param
($param, 'machine');
1742 my $stateuri = extract_param
($param, 'stateuri');
1743 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1744 if $stateuri && $authuser ne 'root@pam';
1746 my $skiplock = extract_param
($param, 'skiplock');
1747 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1748 if $skiplock && $authuser ne 'root@pam';
1750 my $migratedfrom = extract_param
($param, 'migratedfrom');
1751 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1752 if $migratedfrom && $authuser ne 'root@pam';
1754 my $migration_type = extract_param
($param, 'migration_type');
1755 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1756 if $migration_type && $authuser ne 'root@pam';
1758 my $migration_network = extract_param
($param, 'migration_network');
1759 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1760 if $migration_network && $authuser ne 'root@pam';
1762 my $targetstorage = extract_param
($param, 'targetstorage');
1763 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1764 if $targetstorage && $authuser ne 'root@pam';
1766 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1767 if $targetstorage && !$migratedfrom;
1769 # read spice ticket from STDIN
1771 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1772 if (defined(my $line = <>)) {
1774 $spice_ticket = $line;
1778 PVE
::Cluster
::check_cfs_quorum
();
1780 my $storecfg = PVE
::Storage
::config
();
1782 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1783 $rpcenv->{type
} ne 'ha') {
1788 my $service = "vm:$vmid";
1790 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1792 print "Requesting HA start for VM $vmid\n";
1794 PVE
::Tools
::run_command
($cmd);
1799 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1806 syslog
('info', "start VM $vmid: $upid\n");
1808 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1809 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1814 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1818 __PACKAGE__-
>register_method({
1820 path
=> '{vmid}/status/stop',
1824 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1825 "is akin to pulling the power plug of a running computer and may damage the VM data",
1827 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1830 additionalProperties
=> 0,
1832 node
=> get_standard_option
('pve-node'),
1833 vmid
=> get_standard_option
('pve-vmid',
1834 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1835 skiplock
=> get_standard_option
('skiplock'),
1836 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1838 description
=> "Wait maximal timeout seconds.",
1844 description
=> "Do not deactivate storage volumes.",
1857 my $rpcenv = PVE
::RPCEnvironment
::get
();
1859 my $authuser = $rpcenv->get_user();
1861 my $node = extract_param
($param, 'node');
1863 my $vmid = extract_param
($param, 'vmid');
1865 my $skiplock = extract_param
($param, 'skiplock');
1866 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1867 if $skiplock && $authuser ne 'root@pam';
1869 my $keepActive = extract_param
($param, 'keepActive');
1870 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1871 if $keepActive && $authuser ne 'root@pam';
1873 my $migratedfrom = extract_param
($param, 'migratedfrom');
1874 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1875 if $migratedfrom && $authuser ne 'root@pam';
1878 my $storecfg = PVE
::Storage
::config
();
1880 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1885 my $service = "vm:$vmid";
1887 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1889 print "Requesting HA stop for VM $vmid\n";
1891 PVE
::Tools
::run_command
($cmd);
1896 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1902 syslog
('info', "stop VM $vmid: $upid\n");
1904 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1905 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1910 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1914 __PACKAGE__-
>register_method({
1916 path
=> '{vmid}/status/reset',
1920 description
=> "Reset virtual machine.",
1922 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1925 additionalProperties
=> 0,
1927 node
=> get_standard_option
('pve-node'),
1928 vmid
=> get_standard_option
('pve-vmid',
1929 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1930 skiplock
=> get_standard_option
('skiplock'),
1939 my $rpcenv = PVE
::RPCEnvironment
::get
();
1941 my $authuser = $rpcenv->get_user();
1943 my $node = extract_param
($param, 'node');
1945 my $vmid = extract_param
($param, 'vmid');
1947 my $skiplock = extract_param
($param, 'skiplock');
1948 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1949 if $skiplock && $authuser ne 'root@pam';
1951 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1956 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1961 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1964 __PACKAGE__-
>register_method({
1965 name
=> 'vm_shutdown',
1966 path
=> '{vmid}/status/shutdown',
1970 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1971 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1973 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1976 additionalProperties
=> 0,
1978 node
=> get_standard_option
('pve-node'),
1979 vmid
=> get_standard_option
('pve-vmid',
1980 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1981 skiplock
=> get_standard_option
('skiplock'),
1983 description
=> "Wait maximal timeout seconds.",
1989 description
=> "Make sure the VM stops.",
1995 description
=> "Do not deactivate storage volumes.",
2008 my $rpcenv = PVE
::RPCEnvironment
::get
();
2010 my $authuser = $rpcenv->get_user();
2012 my $node = extract_param
($param, 'node');
2014 my $vmid = extract_param
($param, 'vmid');
2016 my $skiplock = extract_param
($param, 'skiplock');
2017 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2018 if $skiplock && $authuser ne 'root@pam';
2020 my $keepActive = extract_param
($param, 'keepActive');
2021 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2022 if $keepActive && $authuser ne 'root@pam';
2024 my $storecfg = PVE
::Storage
::config
();
2028 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2029 # otherwise, we will infer a shutdown command, but run into the timeout,
2030 # then when the vm is resumed, it will instantly shutdown
2032 # checking the qmp status here to get feedback to the gui/cli/api
2033 # and the status query should not take too long
2036 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2040 if (!$err && $qmpstatus->{status
} eq "paused") {
2041 if ($param->{forceStop
}) {
2042 warn "VM is paused - stop instead of shutdown\n";
2045 die "VM is paused - cannot shutdown\n";
2049 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2050 ($rpcenv->{type
} ne 'ha')) {
2055 my $service = "vm:$vmid";
2057 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2059 print "Requesting HA stop for VM $vmid\n";
2061 PVE
::Tools
::run_command
($cmd);
2066 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2073 syslog
('info', "shutdown VM $vmid: $upid\n");
2075 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2076 $shutdown, $param->{forceStop
}, $keepActive);
2081 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2085 __PACKAGE__-
>register_method({
2086 name
=> 'vm_suspend',
2087 path
=> '{vmid}/status/suspend',
2091 description
=> "Suspend virtual machine.",
2093 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2096 additionalProperties
=> 0,
2098 node
=> get_standard_option
('pve-node'),
2099 vmid
=> get_standard_option
('pve-vmid',
2100 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2101 skiplock
=> get_standard_option
('skiplock'),
2110 my $rpcenv = PVE
::RPCEnvironment
::get
();
2112 my $authuser = $rpcenv->get_user();
2114 my $node = extract_param
($param, 'node');
2116 my $vmid = extract_param
($param, 'vmid');
2118 my $skiplock = extract_param
($param, 'skiplock');
2119 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2120 if $skiplock && $authuser ne 'root@pam';
2122 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2127 syslog
('info', "suspend VM $vmid: $upid\n");
2129 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2134 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2137 __PACKAGE__-
>register_method({
2138 name
=> 'vm_resume',
2139 path
=> '{vmid}/status/resume',
2143 description
=> "Resume virtual machine.",
2145 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2148 additionalProperties
=> 0,
2150 node
=> get_standard_option
('pve-node'),
2151 vmid
=> get_standard_option
('pve-vmid',
2152 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2153 skiplock
=> get_standard_option
('skiplock'),
2154 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2164 my $rpcenv = PVE
::RPCEnvironment
::get
();
2166 my $authuser = $rpcenv->get_user();
2168 my $node = extract_param
($param, 'node');
2170 my $vmid = extract_param
($param, 'vmid');
2172 my $skiplock = extract_param
($param, 'skiplock');
2173 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2174 if $skiplock && $authuser ne 'root@pam';
2176 my $nocheck = extract_param
($param, 'nocheck');
2178 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2183 syslog
('info', "resume VM $vmid: $upid\n");
2185 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2190 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2193 __PACKAGE__-
>register_method({
2194 name
=> 'vm_sendkey',
2195 path
=> '{vmid}/sendkey',
2199 description
=> "Send key event to virtual machine.",
2201 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2204 additionalProperties
=> 0,
2206 node
=> get_standard_option
('pve-node'),
2207 vmid
=> get_standard_option
('pve-vmid',
2208 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2209 skiplock
=> get_standard_option
('skiplock'),
2211 description
=> "The key (qemu monitor encoding).",
2216 returns
=> { type
=> 'null'},
2220 my $rpcenv = PVE
::RPCEnvironment
::get
();
2222 my $authuser = $rpcenv->get_user();
2224 my $node = extract_param
($param, 'node');
2226 my $vmid = extract_param
($param, 'vmid');
2228 my $skiplock = extract_param
($param, 'skiplock');
2229 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2230 if $skiplock && $authuser ne 'root@pam';
2232 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2237 __PACKAGE__-
>register_method({
2238 name
=> 'vm_feature',
2239 path
=> '{vmid}/feature',
2243 description
=> "Check if feature for virtual machine is available.",
2245 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2248 additionalProperties
=> 0,
2250 node
=> get_standard_option
('pve-node'),
2251 vmid
=> get_standard_option
('pve-vmid'),
2253 description
=> "Feature to check.",
2255 enum
=> [ 'snapshot', 'clone', 'copy' ],
2257 snapname
=> get_standard_option
('pve-snapshot-name', {
2265 hasFeature
=> { type
=> 'boolean' },
2268 items
=> { type
=> 'string' },
2275 my $node = extract_param
($param, 'node');
2277 my $vmid = extract_param
($param, 'vmid');
2279 my $snapname = extract_param
($param, 'snapname');
2281 my $feature = extract_param
($param, 'feature');
2283 my $running = PVE
::QemuServer
::check_running
($vmid);
2285 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2288 my $snap = $conf->{snapshots
}->{$snapname};
2289 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2292 my $storecfg = PVE
::Storage
::config
();
2294 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2295 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2298 hasFeature
=> $hasFeature,
2299 nodes
=> [ keys %$nodelist ],
2303 __PACKAGE__-
>register_method({
2305 path
=> '{vmid}/clone',
2309 description
=> "Create a copy of virtual machine/template.",
2311 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2312 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2313 "'Datastore.AllocateSpace' on any used storage.",
2316 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2318 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2319 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2324 additionalProperties
=> 0,
2326 node
=> get_standard_option
('pve-node'),
2327 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2328 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2331 type
=> 'string', format
=> 'dns-name',
2332 description
=> "Set a name for the new VM.",
2337 description
=> "Description for the new VM.",
2341 type
=> 'string', format
=> 'pve-poolid',
2342 description
=> "Add the new VM to the specified pool.",
2344 snapname
=> get_standard_option
('pve-snapshot-name', {
2347 storage
=> get_standard_option
('pve-storage-id', {
2348 description
=> "Target storage for full clone.",
2353 description
=> "Target format for file storage.",
2357 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2362 description
=> "Create a full copy of all disk. This is always done when " .
2363 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2366 target
=> get_standard_option
('pve-node', {
2367 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2378 my $rpcenv = PVE
::RPCEnvironment
::get
();
2380 my $authuser = $rpcenv->get_user();
2382 my $node = extract_param
($param, 'node');
2384 my $vmid = extract_param
($param, 'vmid');
2386 my $newid = extract_param
($param, 'newid');
2388 my $pool = extract_param
($param, 'pool');
2390 if (defined($pool)) {
2391 $rpcenv->check_pool_exist($pool);
2394 my $snapname = extract_param
($param, 'snapname');
2396 my $storage = extract_param
($param, 'storage');
2398 my $format = extract_param
($param, 'format');
2400 my $target = extract_param
($param, 'target');
2402 my $localnode = PVE
::INotify
::nodename
();
2404 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2406 PVE
::Cluster
::check_node_exists
($target) if $target;
2408 my $storecfg = PVE
::Storage
::config
();
2411 # check if storage is enabled on local node
2412 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2414 # check if storage is available on target node
2415 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2416 # clone only works if target storage is shared
2417 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2418 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2422 PVE
::Cluster
::check_cfs_quorum
();
2424 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2426 # exclusive lock if VM is running - else shared lock is enough;
2427 my $shared_lock = $running ?
0 : 1;
2431 # do all tests after lock
2432 # we also try to do all tests before we fork the worker
2434 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2436 PVE
::QemuConfig-
>check_lock($conf);
2438 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2440 die "unexpected state change\n" if $verify_running != $running;
2442 die "snapshot '$snapname' does not exist\n"
2443 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2445 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2447 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2449 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2451 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2453 die "unable to create VM $newid: config file already exists\n"
2456 my $newconf = { lock => 'clone' };
2461 foreach my $opt (keys %$oldconf) {
2462 my $value = $oldconf->{$opt};
2464 # do not copy snapshot related info
2465 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2466 $opt eq 'vmstate' || $opt eq 'snapstate';
2468 # no need to copy unused images, because VMID(owner) changes anyways
2469 next if $opt =~ m/^unused\d+$/;
2471 # always change MAC! address
2472 if ($opt =~ m/^net(\d+)$/) {
2473 my $net = PVE
::QemuServer
::parse_net
($value);
2474 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2475 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2476 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2477 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2478 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2479 die "unable to parse drive options for '$opt'\n" if !$drive;
2480 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2481 $newconf->{$opt} = $value; # simply copy configuration
2483 if ($param->{full
}) {
2484 die "Full clone feature is not supported for drive '$opt'\n"
2485 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2486 $fullclone->{$opt} = 1;
2488 # not full means clone instead of copy
2489 die "Linked clone feature is not supported for drive '$opt'\n"
2490 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2492 $drives->{$opt} = $drive;
2493 push @$vollist, $drive->{file
};
2496 # copy everything else
2497 $newconf->{$opt} = $value;
2501 # auto generate a new uuid
2502 my ($uuid, $uuid_str);
2503 UUID
::generate
($uuid);
2504 UUID
::unparse
($uuid, $uuid_str);
2505 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2506 $smbios1->{uuid
} = $uuid_str;
2507 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2509 delete $newconf->{template
};
2511 if ($param->{name
}) {
2512 $newconf->{name
} = $param->{name
};
2514 if ($oldconf->{name
}) {
2515 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2517 $newconf->{name
} = "Copy-of-VM-$vmid";
2521 if ($param->{description
}) {
2522 $newconf->{description
} = $param->{description
};
2525 # create empty/temp config - this fails if VM already exists on other node
2526 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2531 my $newvollist = [];
2538 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2540 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2542 my $total_jobs = scalar(keys %{$drives});
2545 foreach my $opt (keys %$drives) {
2546 my $drive = $drives->{$opt};
2547 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2549 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2550 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2551 $jobs, $skipcomplete, $oldconf->{agent
});
2553 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2555 PVE
::QemuConfig-
>write_config($newid, $newconf);
2559 delete $newconf->{lock};
2560 PVE
::QemuConfig-
>write_config($newid, $newconf);
2563 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2564 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2565 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2567 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2568 die "Failed to move config to node '$target' - rename failed: $!\n"
2569 if !rename($conffile, $newconffile);
2572 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2577 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2579 sleep 1; # some storage like rbd need to wait before release volume - really?
2581 foreach my $volid (@$newvollist) {
2582 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2585 die "clone failed: $err";
2591 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2593 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2596 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2597 # Aquire exclusive lock lock for $newid
2598 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2603 __PACKAGE__-
>register_method({
2604 name
=> 'move_vm_disk',
2605 path
=> '{vmid}/move_disk',
2609 description
=> "Move volume to different storage.",
2611 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2613 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2614 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2618 additionalProperties
=> 0,
2620 node
=> get_standard_option
('pve-node'),
2621 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2624 description
=> "The disk you want to move.",
2625 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2627 storage
=> get_standard_option
('pve-storage-id', {
2628 description
=> "Target storage.",
2629 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2633 description
=> "Target Format.",
2634 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2639 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2645 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2653 description
=> "the task ID.",
2658 my $rpcenv = PVE
::RPCEnvironment
::get
();
2660 my $authuser = $rpcenv->get_user();
2662 my $node = extract_param
($param, 'node');
2664 my $vmid = extract_param
($param, 'vmid');
2666 my $digest = extract_param
($param, 'digest');
2668 my $disk = extract_param
($param, 'disk');
2670 my $storeid = extract_param
($param, 'storage');
2672 my $format = extract_param
($param, 'format');
2674 my $storecfg = PVE
::Storage
::config
();
2676 my $updatefn = sub {
2678 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2680 PVE
::QemuConfig-
>check_lock($conf);
2682 die "checksum missmatch (file change by other user?)\n"
2683 if $digest && $digest ne $conf->{digest
};
2685 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2687 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2689 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2691 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2694 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2695 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2699 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2700 (!$format || !$oldfmt || $oldfmt eq $format);
2702 # this only checks snapshots because $disk is passed!
2703 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2704 die "you can't move a disk with snapshots and delete the source\n"
2705 if $snapshotted && $param->{delete};
2707 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2709 my $running = PVE
::QemuServer
::check_running
($vmid);
2711 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2715 my $newvollist = [];
2718 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2720 warn "moving disk with snapshots, snapshots will not be moved!\n"
2723 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2724 $vmid, $storeid, $format, 1, $newvollist);
2726 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2728 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2730 # convert moved disk to base if part of template
2731 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2732 if PVE
::QemuConfig-
>is_template($conf);
2734 PVE
::QemuConfig-
>write_config($vmid, $conf);
2737 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2738 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2745 foreach my $volid (@$newvollist) {
2746 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2749 die "storage migration failed: $err";
2752 if ($param->{delete}) {
2754 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2755 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2761 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2764 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2767 __PACKAGE__-
>register_method({
2768 name
=> 'migrate_vm',
2769 path
=> '{vmid}/migrate',
2773 description
=> "Migrate virtual machine. Creates a new migration task.",
2775 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2778 additionalProperties
=> 0,
2780 node
=> get_standard_option
('pve-node'),
2781 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2782 target
=> get_standard_option
('pve-node', {
2783 description
=> "Target node.",
2784 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2788 description
=> "Use online/live migration.",
2793 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2798 enum
=> ['secure', 'insecure'],
2799 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2802 migration_network
=> {
2803 type
=> 'string', format
=> 'CIDR',
2804 description
=> "CIDR of the (sub) network that is used for migration.",
2807 "with-local-disks" => {
2809 description
=> "Enable live storage migration for local disk",
2812 targetstorage
=> get_standard_option
('pve-storage-id', {
2813 description
=> "Default target storage.",
2815 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2821 description
=> "the task ID.",
2826 my $rpcenv = PVE
::RPCEnvironment
::get
();
2828 my $authuser = $rpcenv->get_user();
2830 my $target = extract_param
($param, 'target');
2832 my $localnode = PVE
::INotify
::nodename
();
2833 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2835 PVE
::Cluster
::check_cfs_quorum
();
2837 PVE
::Cluster
::check_node_exists
($target);
2839 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2841 my $vmid = extract_param
($param, 'vmid');
2843 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2844 if !$param->{online
} && $param->{targetstorage
};
2846 raise_param_exc
({ force
=> "Only root may use this option." })
2847 if $param->{force
} && $authuser ne 'root@pam';
2849 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2850 if $param->{migration_type
} && $authuser ne 'root@pam';
2852 # allow root only until better network permissions are available
2853 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2854 if $param->{migration_network
} && $authuser ne 'root@pam';
2857 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2859 # try to detect errors early
2861 PVE
::QemuConfig-
>check_lock($conf);
2863 if (PVE
::QemuServer
::check_running
($vmid)) {
2864 die "cant migrate running VM without --online\n"
2865 if !$param->{online
};
2868 my $storecfg = PVE
::Storage
::config
();
2870 if( $param->{targetstorage
}) {
2871 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2873 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2876 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2881 my $service = "vm:$vmid";
2883 my $cmd = ['ha-manager', 'migrate', $service, $target];
2885 print "Requesting HA migration for VM $vmid to node $target\n";
2887 PVE
::Tools
::run_command
($cmd);
2892 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2897 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2901 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2904 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2909 __PACKAGE__-
>register_method({
2911 path
=> '{vmid}/monitor',
2915 description
=> "Execute Qemu monitor commands.",
2917 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2918 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2921 additionalProperties
=> 0,
2923 node
=> get_standard_option
('pve-node'),
2924 vmid
=> get_standard_option
('pve-vmid'),
2927 description
=> "The monitor command.",
2931 returns
=> { type
=> 'string'},
2935 my $rpcenv = PVE
::RPCEnvironment
::get
();
2936 my $authuser = $rpcenv->get_user();
2939 my $command = shift;
2940 return $command =~ m/^\s*info(\s+|$)/
2941 || $command =~ m/^\s*help\s*$/;
2944 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2945 if !&$is_ro($param->{command
});
2947 my $vmid = $param->{vmid
};
2949 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2953 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2955 $res = "ERROR: $@" if $@;
2960 my $guest_agent_commands = [
2968 'network-get-interfaces',
2971 'get-memory-blocks',
2972 'get-memory-block-info',
2979 __PACKAGE__-
>register_method({
2981 path
=> '{vmid}/agent',
2985 description
=> "Execute Qemu Guest Agent commands.",
2987 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2990 additionalProperties
=> 0,
2992 node
=> get_standard_option
('pve-node'),
2993 vmid
=> get_standard_option
('pve-vmid', {
2994 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2997 description
=> "The QGA command.",
2998 enum
=> $guest_agent_commands,
3004 description
=> "Returns an object with a single `result` property. The type of that
3005 property depends on the executed command.",
3010 my $vmid = $param->{vmid
};
3012 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3014 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3015 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3017 my $cmd = $param->{command
};
3019 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3021 return { result
=> $res };
3024 __PACKAGE__-
>register_method({
3025 name
=> 'resize_vm',
3026 path
=> '{vmid}/resize',
3030 description
=> "Extend volume size.",
3032 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3035 additionalProperties
=> 0,
3037 node
=> get_standard_option
('pve-node'),
3038 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3039 skiplock
=> get_standard_option
('skiplock'),
3042 description
=> "The disk you want to resize.",
3043 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3047 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3048 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.",
3052 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3058 returns
=> { type
=> 'null'},
3062 my $rpcenv = PVE
::RPCEnvironment
::get
();
3064 my $authuser = $rpcenv->get_user();
3066 my $node = extract_param
($param, 'node');
3068 my $vmid = extract_param
($param, 'vmid');
3070 my $digest = extract_param
($param, 'digest');
3072 my $disk = extract_param
($param, 'disk');
3074 my $sizestr = extract_param
($param, 'size');
3076 my $skiplock = extract_param
($param, 'skiplock');
3077 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3078 if $skiplock && $authuser ne 'root@pam';
3080 my $storecfg = PVE
::Storage
::config
();
3082 my $updatefn = sub {
3084 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3086 die "checksum missmatch (file change by other user?)\n"
3087 if $digest && $digest ne $conf->{digest
};
3088 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3090 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3092 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3094 my (undef, undef, undef, undef, undef, undef, $format) =
3095 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3097 die "can't resize volume: $disk if snapshot exists\n"
3098 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3100 my $volid = $drive->{file
};
3102 die "disk '$disk' has no associated volume\n" if !$volid;
3104 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3106 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3108 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3110 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3111 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3113 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3114 my ($ext, $newsize, $unit) = ($1, $2, $4);
3117 $newsize = $newsize * 1024;
3118 } elsif ($unit eq 'M') {
3119 $newsize = $newsize * 1024 * 1024;
3120 } elsif ($unit eq 'G') {
3121 $newsize = $newsize * 1024 * 1024 * 1024;
3122 } elsif ($unit eq 'T') {
3123 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3126 $newsize += $size if $ext;
3127 $newsize = int($newsize);
3129 die "shrinking disks is not supported\n" if $newsize < $size;
3131 return if $size == $newsize;
3133 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3135 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3137 $drive->{size
} = $newsize;
3138 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3140 PVE
::QemuConfig-
>write_config($vmid, $conf);
3143 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3147 __PACKAGE__-
>register_method({
3148 name
=> 'snapshot_list',
3149 path
=> '{vmid}/snapshot',
3151 description
=> "List all snapshots.",
3153 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3156 protected
=> 1, # qemu pid files are only readable by root
3158 additionalProperties
=> 0,
3160 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3161 node
=> get_standard_option
('pve-node'),
3170 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3175 my $vmid = $param->{vmid
};
3177 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3178 my $snaphash = $conf->{snapshots
} || {};
3182 foreach my $name (keys %$snaphash) {
3183 my $d = $snaphash->{$name};
3186 snaptime
=> $d->{snaptime
} || 0,
3187 vmstate
=> $d->{vmstate
} ?
1 : 0,
3188 description
=> $d->{description
} || '',
3190 $item->{parent
} = $d->{parent
} if $d->{parent
};
3191 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3195 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3196 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3197 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3199 push @$res, $current;
3204 __PACKAGE__-
>register_method({
3206 path
=> '{vmid}/snapshot',
3210 description
=> "Snapshot a VM.",
3212 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3215 additionalProperties
=> 0,
3217 node
=> get_standard_option
('pve-node'),
3218 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3219 snapname
=> get_standard_option
('pve-snapshot-name'),
3223 description
=> "Save the vmstate",
3228 description
=> "A textual description or comment.",
3234 description
=> "the task ID.",
3239 my $rpcenv = PVE
::RPCEnvironment
::get
();
3241 my $authuser = $rpcenv->get_user();
3243 my $node = extract_param
($param, 'node');
3245 my $vmid = extract_param
($param, 'vmid');
3247 my $snapname = extract_param
($param, 'snapname');
3249 die "unable to use snapshot name 'current' (reserved name)\n"
3250 if $snapname eq 'current';
3253 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3254 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3255 $param->{description
});
3258 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3261 __PACKAGE__-
>register_method({
3262 name
=> 'snapshot_cmd_idx',
3263 path
=> '{vmid}/snapshot/{snapname}',
3270 additionalProperties
=> 0,
3272 vmid
=> get_standard_option
('pve-vmid'),
3273 node
=> get_standard_option
('pve-node'),
3274 snapname
=> get_standard_option
('pve-snapshot-name'),
3283 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3290 push @$res, { cmd
=> 'rollback' };
3291 push @$res, { cmd
=> 'config' };
3296 __PACKAGE__-
>register_method({
3297 name
=> 'update_snapshot_config',
3298 path
=> '{vmid}/snapshot/{snapname}/config',
3302 description
=> "Update snapshot metadata.",
3304 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3307 additionalProperties
=> 0,
3309 node
=> get_standard_option
('pve-node'),
3310 vmid
=> get_standard_option
('pve-vmid'),
3311 snapname
=> get_standard_option
('pve-snapshot-name'),
3315 description
=> "A textual description or comment.",
3319 returns
=> { type
=> 'null' },
3323 my $rpcenv = PVE
::RPCEnvironment
::get
();
3325 my $authuser = $rpcenv->get_user();
3327 my $vmid = extract_param
($param, 'vmid');
3329 my $snapname = extract_param
($param, 'snapname');
3331 return undef if !defined($param->{description
});
3333 my $updatefn = sub {
3335 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3337 PVE
::QemuConfig-
>check_lock($conf);
3339 my $snap = $conf->{snapshots
}->{$snapname};
3341 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3343 $snap->{description
} = $param->{description
} if defined($param->{description
});
3345 PVE
::QemuConfig-
>write_config($vmid, $conf);
3348 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3353 __PACKAGE__-
>register_method({
3354 name
=> 'get_snapshot_config',
3355 path
=> '{vmid}/snapshot/{snapname}/config',
3358 description
=> "Get snapshot configuration",
3360 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3363 additionalProperties
=> 0,
3365 node
=> get_standard_option
('pve-node'),
3366 vmid
=> get_standard_option
('pve-vmid'),
3367 snapname
=> get_standard_option
('pve-snapshot-name'),
3370 returns
=> { type
=> "object" },
3374 my $rpcenv = PVE
::RPCEnvironment
::get
();
3376 my $authuser = $rpcenv->get_user();
3378 my $vmid = extract_param
($param, 'vmid');
3380 my $snapname = extract_param
($param, 'snapname');
3382 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3384 my $snap = $conf->{snapshots
}->{$snapname};
3386 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3391 __PACKAGE__-
>register_method({
3393 path
=> '{vmid}/snapshot/{snapname}/rollback',
3397 description
=> "Rollback VM state to specified snapshot.",
3399 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3402 additionalProperties
=> 0,
3404 node
=> get_standard_option
('pve-node'),
3405 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3406 snapname
=> get_standard_option
('pve-snapshot-name'),
3411 description
=> "the task ID.",
3416 my $rpcenv = PVE
::RPCEnvironment
::get
();
3418 my $authuser = $rpcenv->get_user();
3420 my $node = extract_param
($param, 'node');
3422 my $vmid = extract_param
($param, 'vmid');
3424 my $snapname = extract_param
($param, 'snapname');
3427 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3428 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3432 # hold migration lock, this makes sure that nobody create replication snapshots
3433 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3436 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3439 __PACKAGE__-
>register_method({
3440 name
=> 'delsnapshot',
3441 path
=> '{vmid}/snapshot/{snapname}',
3445 description
=> "Delete a VM snapshot.",
3447 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3450 additionalProperties
=> 0,
3452 node
=> get_standard_option
('pve-node'),
3453 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3454 snapname
=> get_standard_option
('pve-snapshot-name'),
3458 description
=> "For removal from config file, even if removing disk snapshots fails.",
3464 description
=> "the task ID.",
3469 my $rpcenv = PVE
::RPCEnvironment
::get
();
3471 my $authuser = $rpcenv->get_user();
3473 my $node = extract_param
($param, 'node');
3475 my $vmid = extract_param
($param, 'vmid');
3477 my $snapname = extract_param
($param, 'snapname');
3480 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3481 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3484 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3487 __PACKAGE__-
>register_method({
3489 path
=> '{vmid}/template',
3493 description
=> "Create a Template.",
3495 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3496 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3499 additionalProperties
=> 0,
3501 node
=> get_standard_option
('pve-node'),
3502 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3506 description
=> "If you want to convert only 1 disk to base image.",
3507 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3512 returns
=> { type
=> 'null'},
3516 my $rpcenv = PVE
::RPCEnvironment
::get
();
3518 my $authuser = $rpcenv->get_user();
3520 my $node = extract_param
($param, 'node');
3522 my $vmid = extract_param
($param, 'vmid');
3524 my $disk = extract_param
($param, 'disk');
3526 my $updatefn = sub {
3528 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3530 PVE
::QemuConfig-
>check_lock($conf);
3532 die "unable to create template, because VM contains snapshots\n"
3533 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3535 die "you can't convert a template to a template\n"
3536 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3538 die "you can't convert a VM to template if VM is running\n"
3539 if PVE
::QemuServer
::check_running
($vmid);
3542 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3545 $conf->{template
} = 1;
3546 PVE
::QemuConfig-
>write_config($vmid, $conf);
3548 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3551 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);