1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
21 use Data
::Dumper
; # fixme: remove
23 use base
qw(PVE::RESTHandler);
25 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.";
27 my $resolve_cdrom_alias = sub {
30 if (my $value = $param->{cdrom
}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2
} = $value;
33 delete $param->{cdrom
};
38 my $check_storage_access = sub {
39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
41 PVE
::QemuServer
::foreach_drive
($settings, sub {
42 my ($ds, $drive) = @_;
44 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
46 my $volid = $drive->{file
};
48 if (!$volid || $volid eq 'none') {
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
62 my $check_storage_access_copy = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
65 PVE
::QemuServer
::foreach_drive
($conf, sub {
66 my ($ds, $drive) = @_;
68 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
70 my $volid = $drive->{file
};
72 return if !$volid || $volid eq 'none';
75 if ($volid eq 'cdrom') {
76 $rpcenv->check($authuser, "/", ['Sys.Console']);
78 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
82 die "unable to copy arbitrary files\n" if !$sid;
83 $sid = $storage if $storage;
84 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
89 # Note: $pool is only needed when creating a VM, because pool permissions
90 # are automatically inherited if VM already exists inside a pool.
91 my $create_disks = sub {
92 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
97 PVE
::QemuServer
::foreach_drive
($settings, sub {
100 my $volid = $disk->{file
};
102 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
103 delete $disk->{size
};
104 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
105 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
106 my ($storeid, $size) = ($2 || $default_storage, $3);
107 die "no storage ID specified (and no default storage)\n" if !$storeid;
108 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
109 my $fmt = $disk->{format
} || $defformat;
110 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
111 $fmt, undef, $size*1024*1024);
112 $disk->{file
} = $volid;
113 $disk->{size
} = $size*1024*1024*1024;
114 push @$vollist, $volid;
115 delete $disk->{format
}; # no longer needed
116 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
119 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
121 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
123 my $foundvolid = undef;
126 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
127 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
129 PVE
::Storage
::foreach_volid
($dl, sub {
131 if($volumeid eq $volid) {
138 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
140 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
141 $disk->{size
} = $size;
142 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
146 # free allocated images on error
148 syslog
('err', "VM $vmid creating disks failed");
149 foreach my $volid (@$vollist) {
150 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
156 # modify vm config if everything went well
157 foreach my $ds (keys %$res) {
158 $conf->{$ds} = $res->{$ds};
164 my $check_vm_modify_config_perm = sub {
165 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
167 return 1 if $authuser eq 'root@pam';
169 foreach my $opt (@$key_list) {
170 # disk checks need to be done somewhere else
171 next if PVE
::QemuServer
::valid_drivename
($opt);
173 if ($opt eq 'sockets' || $opt eq 'cores' ||
174 $opt eq 'cpu' || $opt eq 'smp' ||
175 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
176 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
177 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
178 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
179 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
180 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
181 } elsif ($opt eq 'args' || $opt eq 'lock') {
182 die "only root can set '$opt' config\n";
183 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
184 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
185 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
186 } elsif ($opt =~ m/^net\d+$/) {
187 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
196 __PACKAGE__-
>register_method({
200 description
=> "Virtual machine index (per node).",
202 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
206 protected
=> 1, # qemu pid files are only readable by root
208 additionalProperties
=> 0,
210 node
=> get_standard_option
('pve-node'),
219 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
224 my $rpcenv = PVE
::RPCEnvironment
::get
();
225 my $authuser = $rpcenv->get_user();
227 my $vmstatus = PVE
::QemuServer
::vmstatus
();
230 foreach my $vmid (keys %$vmstatus) {
231 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
233 my $data = $vmstatus->{$vmid};
234 $data->{vmid
} = $vmid;
241 __PACKAGE__-
>register_method({
245 description
=> "Create or restore a virtual machine.",
247 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
249 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
250 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
256 additionalProperties
=> 0,
257 properties
=> PVE
::QemuServer
::json_config_properties
(
259 node
=> get_standard_option
('pve-node'),
260 vmid
=> get_standard_option
('pve-vmid'),
262 description
=> "The backup file.",
267 storage
=> get_standard_option
('pve-storage-id', {
268 description
=> "Default storage.",
274 description
=> "Allow to overwrite existing VM.",
275 requires
=> 'archive',
280 description
=> "Assign a unique random ethernet address.",
281 requires
=> 'archive',
285 type
=> 'string', format
=> 'pve-poolid',
286 description
=> "Add the VM to the specified pool.",
296 my $rpcenv = PVE
::RPCEnvironment
::get
();
298 my $authuser = $rpcenv->get_user();
300 my $node = extract_param
($param, 'node');
302 my $vmid = extract_param
($param, 'vmid');
304 my $archive = extract_param
($param, 'archive');
306 my $storage = extract_param
($param, 'storage');
308 my $force = extract_param
($param, 'force');
310 my $unique = extract_param
($param, 'unique');
312 my $pool = extract_param
($param, 'pool');
314 my $filename = PVE
::QemuServer
::config_file
($vmid);
316 my $storecfg = PVE
::Storage
::config
();
318 PVE
::Cluster
::check_cfs_quorum
();
320 if (defined($pool)) {
321 $rpcenv->check_pool_exist($pool);
324 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
325 if defined($storage);
328 &$resolve_cdrom_alias($param);
330 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
332 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
334 foreach my $opt (keys %$param) {
335 if (PVE
::QemuServer
::valid_drivename
($opt)) {
336 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
337 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
339 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
340 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
344 PVE
::QemuServer
::add_random_macs
($param);
346 my $keystr = join(' ', keys %$param);
347 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
349 if ($archive eq '-') {
350 die "pipe requires cli environment\n"
351 if $rpcenv->{type
} ne 'cli';
353 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
355 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
356 if PVE
::Storage
::parse_volume_id
($archive, 1);
358 die "can't find archive file '$archive'\n" if !($path && -f
$path);
363 my $addVMtoPoolFn = sub {
364 my $usercfg = cfs_read_file
("user.cfg");
365 if (my $data = $usercfg->{pools
}->{$pool}) {
366 $data->{vms
}->{$vmid} = 1;
367 $usercfg->{vms
}->{$vmid} = $pool;
368 cfs_write_file
("user.cfg", $usercfg);
372 my $restorefn = sub {
374 # fixme: this test does not work if VM exists on other node!
376 die "unable to restore vm $vmid: config file already exists\n"
379 die "unable to restore vm $vmid: vm is running\n"
380 if PVE
::QemuServer
::check_running
($vmid);
384 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
387 unique
=> $unique });
389 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
392 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
398 die "unable to create vm $vmid: config file already exists\n"
409 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
411 # try to be smart about bootdisk
412 my @disks = PVE
::QemuServer
::disknames
();
414 foreach my $ds (reverse @disks) {
415 next if !$conf->{$ds};
416 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
417 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
421 if (!$conf->{bootdisk
} && $firstdisk) {
422 $conf->{bootdisk
} = $firstdisk;
425 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
431 foreach my $volid (@$vollist) {
432 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
435 die "create failed - $err";
438 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
441 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
444 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
447 __PACKAGE__-
>register_method({
452 description
=> "Directory index",
457 additionalProperties
=> 0,
459 node
=> get_standard_option
('pve-node'),
460 vmid
=> get_standard_option
('pve-vmid'),
468 subdir
=> { type
=> 'string' },
471 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
477 { subdir
=> 'config' },
478 { subdir
=> 'status' },
479 { subdir
=> 'unlink' },
480 { subdir
=> 'vncproxy' },
481 { subdir
=> 'migrate' },
482 { subdir
=> 'resize' },
484 { subdir
=> 'rrddata' },
485 { subdir
=> 'monitor' },
486 { subdir
=> 'snapshot' },
492 __PACKAGE__-
>register_method({
494 path
=> '{vmid}/rrd',
496 protected
=> 1, # fixme: can we avoid that?
498 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
500 description
=> "Read VM RRD statistics (returns PNG)",
502 additionalProperties
=> 0,
504 node
=> get_standard_option
('pve-node'),
505 vmid
=> get_standard_option
('pve-vmid'),
507 description
=> "Specify the time frame you are interested in.",
509 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
512 description
=> "The list of datasources you want to display.",
513 type
=> 'string', format
=> 'pve-configid-list',
516 description
=> "The RRD consolidation function",
518 enum
=> [ 'AVERAGE', 'MAX' ],
526 filename
=> { type
=> 'string' },
532 return PVE
::Cluster
::create_rrd_graph
(
533 "pve2-vm/$param->{vmid}", $param->{timeframe
},
534 $param->{ds
}, $param->{cf
});
538 __PACKAGE__-
>register_method({
540 path
=> '{vmid}/rrddata',
542 protected
=> 1, # fixme: can we avoid that?
544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
546 description
=> "Read VM RRD statistics",
548 additionalProperties
=> 0,
550 node
=> get_standard_option
('pve-node'),
551 vmid
=> get_standard_option
('pve-vmid'),
553 description
=> "Specify the time frame you are interested in.",
555 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
558 description
=> "The RRD consolidation function",
560 enum
=> [ 'AVERAGE', 'MAX' ],
575 return PVE
::Cluster
::create_rrd_data
(
576 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
580 __PACKAGE__-
>register_method({
582 path
=> '{vmid}/config',
585 description
=> "Get virtual machine configuration.",
587 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
590 additionalProperties
=> 0,
592 node
=> get_standard_option
('pve-node'),
593 vmid
=> get_standard_option
('pve-vmid'),
601 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
608 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
610 delete $conf->{snapshots
};
615 my $vm_is_volid_owner = sub {
616 my ($storecfg, $vmid, $volid) =@_;
618 if ($volid !~ m
|^/|) {
620 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
621 if ($owner && ($owner == $vmid)) {
629 my $test_deallocate_drive = sub {
630 my ($storecfg, $vmid, $key, $drive, $force) = @_;
632 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
633 my $volid = $drive->{file
};
634 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
635 if ($force || $key =~ m/^unused/) {
636 my $sid = PVE
::Storage
::parse_volume_id
($volid);
645 my $delete_drive = sub {
646 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
648 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
649 my $volid = $drive->{file
};
650 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
651 if ($force || $key =~ m/^unused/) {
652 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
655 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
660 delete $conf->{$key};
663 my $vmconfig_delete_option = sub {
664 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
666 return if !defined($conf->{$opt});
668 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
671 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
673 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
674 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
675 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
679 my $unplugwarning = "";
680 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
681 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
682 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
683 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
684 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
685 $unplugwarning = "<br>verify that your guest support acpi hotplug";
688 if($opt eq 'tablet'){
689 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
691 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
695 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
696 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
698 delete $conf->{$opt};
701 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
704 my $safe_num_ne = sub {
707 return 0 if !defined($a) && !defined($b);
708 return 1 if !defined($a);
709 return 1 if !defined($b);
714 my $vmconfig_update_disk = sub {
715 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
717 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
719 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
720 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
722 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
727 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
729 my $media = $drive->{media
} || 'disk';
730 my $oldmedia = $old_drive->{media
} || 'disk';
731 die "unable to change media type\n" if $media ne $oldmedia;
733 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
734 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
736 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
737 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
740 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
741 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
742 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
743 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
744 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
745 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
746 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
747 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
748 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
749 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
754 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
755 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
757 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
758 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
760 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
762 if (PVE
::QemuServer
::check_running
($vmid)) {
763 if ($drive->{file
} eq 'none') {
764 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
766 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
767 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
768 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
772 } else { # hotplug new disks
774 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
778 my $vmconfig_update_net = sub {
779 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
781 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
782 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
783 my $newnet = PVE
::QemuServer
::parse_net
($value);
785 if($oldnet->{model
} ne $newnet->{model
}){
786 #if model change, we try to hot-unplug
787 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
790 if($newnet->{bridge
} && $oldnet->{bridge
}){
791 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
793 if($newnet->{rate
} ne $oldnet->{rate
}){
794 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
797 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
798 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
799 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
803 #if bridge/nat mode change, we try to hot-unplug
804 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
809 $conf->{$opt} = $value;
810 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
811 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
813 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
815 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
818 my $vm_config_perm_list = [
828 __PACKAGE__-
>register_method({
830 path
=> '{vmid}/config',
834 description
=> "Set virtual machine options.",
836 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
839 additionalProperties
=> 0,
840 properties
=> PVE
::QemuServer
::json_config_properties
(
842 node
=> get_standard_option
('pve-node'),
843 vmid
=> get_standard_option
('pve-vmid'),
844 skiplock
=> get_standard_option
('skiplock'),
846 type
=> 'string', format
=> 'pve-configid-list',
847 description
=> "A list of settings you want to delete.",
852 description
=> $opt_force_description,
854 requires
=> 'delete',
858 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
864 returns
=> { type
=> 'null'},
868 my $rpcenv = PVE
::RPCEnvironment
::get
();
870 my $authuser = $rpcenv->get_user();
872 my $node = extract_param
($param, 'node');
874 my $vmid = extract_param
($param, 'vmid');
876 my $digest = extract_param
($param, 'digest');
878 my @paramarr = (); # used for log message
879 foreach my $key (keys %$param) {
880 push @paramarr, "-$key", $param->{$key};
883 my $skiplock = extract_param
($param, 'skiplock');
884 raise_param_exc
({ skiplock
=> "Only root may use this option." })
885 if $skiplock && $authuser ne 'root@pam';
887 my $delete_str = extract_param
($param, 'delete');
889 my $force = extract_param
($param, 'force');
891 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
893 my $storecfg = PVE
::Storage
::config
();
895 my $defaults = PVE
::QemuServer
::load_defaults
();
897 &$resolve_cdrom_alias($param);
899 # now try to verify all parameters
902 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
903 $opt = 'ide2' if $opt eq 'cdrom';
904 raise_param_exc
({ delete => "you can't use '-$opt' and " .
905 "-delete $opt' at the same time" })
906 if defined($param->{$opt});
908 if (!PVE
::QemuServer
::option_exists
($opt)) {
909 raise_param_exc
({ delete => "unknown option '$opt'" });
915 foreach my $opt (keys %$param) {
916 if (PVE
::QemuServer
::valid_drivename
($opt)) {
918 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
919 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
920 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
921 } elsif ($opt =~ m/^net(\d+)$/) {
923 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
924 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
928 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
930 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
932 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
936 my $conf = PVE
::QemuServer
::load_config
($vmid);
938 die "checksum missmatch (file change by other user?)\n"
939 if $digest && $digest ne $conf->{digest
};
941 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
943 if ($param->{memory
} || defined($param->{balloon
})) {
944 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
945 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
947 die "balloon value too large (must be smaller than assigned memory)\n"
948 if $balloon > $maxmem;
951 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
953 foreach my $opt (@delete) { # delete
954 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
955 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
958 my $running = PVE
::QemuServer
::check_running
($vmid);
960 foreach my $opt (keys %$param) { # add/change
962 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
964 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
966 if (PVE
::QemuServer
::valid_drivename
($opt)) {
968 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
969 $opt, $param->{$opt}, $force);
971 } elsif ($opt =~ m/^net(\d+)$/) { #nics
973 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
974 $opt, $param->{$opt});
978 if($opt eq 'tablet' && $param->{$opt} == 1){
979 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
980 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
981 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
984 $conf->{$opt} = $param->{$opt};
985 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
989 # allow manual ballooning if shares is set to zero
990 if ($running && defined($param->{balloon
}) &&
991 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
992 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
993 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
998 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1004 __PACKAGE__-
>register_method({
1005 name
=> 'destroy_vm',
1010 description
=> "Destroy the vm (also delete all used/owned volumes).",
1012 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1015 additionalProperties
=> 0,
1017 node
=> get_standard_option
('pve-node'),
1018 vmid
=> get_standard_option
('pve-vmid'),
1019 skiplock
=> get_standard_option
('skiplock'),
1028 my $rpcenv = PVE
::RPCEnvironment
::get
();
1030 my $authuser = $rpcenv->get_user();
1032 my $vmid = $param->{vmid
};
1034 my $skiplock = $param->{skiplock
};
1035 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1036 if $skiplock && $authuser ne 'root@pam';
1039 my $conf = PVE
::QemuServer
::load_config
($vmid);
1041 my $storecfg = PVE
::Storage
::config
();
1043 my $delVMfromPoolFn = sub {
1044 my $usercfg = cfs_read_file
("user.cfg");
1045 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1046 if (my $data = $usercfg->{pools
}->{$pool}) {
1047 delete $data->{vms
}->{$vmid};
1048 delete $usercfg->{vms
}->{$vmid};
1049 cfs_write_file
("user.cfg", $usercfg);
1057 syslog
('info', "destroy VM $vmid: $upid\n");
1059 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1061 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
1064 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1067 __PACKAGE__-
>register_method({
1069 path
=> '{vmid}/unlink',
1073 description
=> "Unlink/delete disk images.",
1075 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1078 additionalProperties
=> 0,
1080 node
=> get_standard_option
('pve-node'),
1081 vmid
=> get_standard_option
('pve-vmid'),
1083 type
=> 'string', format
=> 'pve-configid-list',
1084 description
=> "A list of disk IDs you want to delete.",
1088 description
=> $opt_force_description,
1093 returns
=> { type
=> 'null'},
1097 $param->{delete} = extract_param
($param, 'idlist');
1099 __PACKAGE__-
>update_vm($param);
1106 __PACKAGE__-
>register_method({
1108 path
=> '{vmid}/vncproxy',
1112 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1114 description
=> "Creates a TCP VNC proxy connections.",
1116 additionalProperties
=> 0,
1118 node
=> get_standard_option
('pve-node'),
1119 vmid
=> get_standard_option
('pve-vmid'),
1123 additionalProperties
=> 0,
1125 user
=> { type
=> 'string' },
1126 ticket
=> { type
=> 'string' },
1127 cert
=> { type
=> 'string' },
1128 port
=> { type
=> 'integer' },
1129 upid
=> { type
=> 'string' },
1135 my $rpcenv = PVE
::RPCEnvironment
::get
();
1137 my $authuser = $rpcenv->get_user();
1139 my $vmid = $param->{vmid
};
1140 my $node = $param->{node
};
1142 my $authpath = "/vms/$vmid";
1144 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1146 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1149 my $port = PVE
::Tools
::next_vnc_port
();
1153 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1154 $remip = PVE
::Cluster
::remote_node_ip
($node);
1157 # NOTE: kvm VNC traffic is already TLS encrypted
1158 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1165 syslog
('info', "starting vnc proxy $upid\n");
1167 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1169 my $qmstr = join(' ', @$qmcmd);
1171 # also redirect stderr (else we get RFB protocol errors)
1172 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1174 PVE
::Tools
::run_command
($cmd);
1179 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1181 PVE
::Tools
::wait_for_vnc_port
($port);
1192 __PACKAGE__-
>register_method({
1194 path
=> '{vmid}/status',
1197 description
=> "Directory index",
1202 additionalProperties
=> 0,
1204 node
=> get_standard_option
('pve-node'),
1205 vmid
=> get_standard_option
('pve-vmid'),
1213 subdir
=> { type
=> 'string' },
1216 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1222 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1225 { subdir
=> 'current' },
1226 { subdir
=> 'start' },
1227 { subdir
=> 'stop' },
1233 my $vm_is_ha_managed = sub {
1236 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1237 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1243 __PACKAGE__-
>register_method({
1244 name
=> 'vm_status',
1245 path
=> '{vmid}/status/current',
1248 protected
=> 1, # qemu pid files are only readable by root
1249 description
=> "Get virtual machine status.",
1251 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1254 additionalProperties
=> 0,
1256 node
=> get_standard_option
('pve-node'),
1257 vmid
=> get_standard_option
('pve-vmid'),
1260 returns
=> { type
=> 'object' },
1265 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1267 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1268 my $status = $vmstatus->{$param->{vmid
}};
1270 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1275 __PACKAGE__-
>register_method({
1277 path
=> '{vmid}/status/start',
1281 description
=> "Start virtual machine.",
1283 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1286 additionalProperties
=> 0,
1288 node
=> get_standard_option
('pve-node'),
1289 vmid
=> get_standard_option
('pve-vmid'),
1290 skiplock
=> get_standard_option
('skiplock'),
1291 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1292 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1302 my $rpcenv = PVE
::RPCEnvironment
::get
();
1304 my $authuser = $rpcenv->get_user();
1306 my $node = extract_param
($param, 'node');
1308 my $vmid = extract_param
($param, 'vmid');
1310 my $stateuri = extract_param
($param, 'stateuri');
1311 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1312 if $stateuri && $authuser ne 'root@pam';
1314 my $skiplock = extract_param
($param, 'skiplock');
1315 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1316 if $skiplock && $authuser ne 'root@pam';
1318 my $migratedfrom = extract_param
($param, 'migratedfrom');
1319 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1320 if $migratedfrom && $authuser ne 'root@pam';
1322 my $storecfg = PVE
::Storage
::config
();
1324 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1325 $rpcenv->{type
} ne 'ha') {
1330 my $service = "pvevm:$vmid";
1332 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1334 print "Executing HA start for VM $vmid\n";
1336 PVE
::Tools
::run_command
($cmd);
1341 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1348 syslog
('info', "start VM $vmid: $upid\n");
1350 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1355 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1359 __PACKAGE__-
>register_method({
1361 path
=> '{vmid}/status/stop',
1365 description
=> "Stop virtual machine.",
1367 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1370 additionalProperties
=> 0,
1372 node
=> get_standard_option
('pve-node'),
1373 vmid
=> get_standard_option
('pve-vmid'),
1374 skiplock
=> get_standard_option
('skiplock'),
1375 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1377 description
=> "Wait maximal timeout seconds.",
1383 description
=> "Do not decativate storage volumes.",
1396 my $rpcenv = PVE
::RPCEnvironment
::get
();
1398 my $authuser = $rpcenv->get_user();
1400 my $node = extract_param
($param, 'node');
1402 my $vmid = extract_param
($param, 'vmid');
1404 my $skiplock = extract_param
($param, 'skiplock');
1405 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1406 if $skiplock && $authuser ne 'root@pam';
1408 my $keepActive = extract_param
($param, 'keepActive');
1409 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1410 if $keepActive && $authuser ne 'root@pam';
1412 my $migratedfrom = extract_param
($param, 'migratedfrom');
1413 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1414 if $migratedfrom && $authuser ne 'root@pam';
1417 my $storecfg = PVE
::Storage
::config
();
1419 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1424 my $service = "pvevm:$vmid";
1426 my $cmd = ['clusvcadm', '-d', $service];
1428 print "Executing HA stop for VM $vmid\n";
1430 PVE
::Tools
::run_command
($cmd);
1435 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1441 syslog
('info', "stop VM $vmid: $upid\n");
1443 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1444 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1449 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1453 __PACKAGE__-
>register_method({
1455 path
=> '{vmid}/status/reset',
1459 description
=> "Reset virtual machine.",
1461 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1464 additionalProperties
=> 0,
1466 node
=> get_standard_option
('pve-node'),
1467 vmid
=> get_standard_option
('pve-vmid'),
1468 skiplock
=> get_standard_option
('skiplock'),
1477 my $rpcenv = PVE
::RPCEnvironment
::get
();
1479 my $authuser = $rpcenv->get_user();
1481 my $node = extract_param
($param, 'node');
1483 my $vmid = extract_param
($param, 'vmid');
1485 my $skiplock = extract_param
($param, 'skiplock');
1486 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1487 if $skiplock && $authuser ne 'root@pam';
1489 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1494 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1499 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1502 __PACKAGE__-
>register_method({
1503 name
=> 'vm_shutdown',
1504 path
=> '{vmid}/status/shutdown',
1508 description
=> "Shutdown virtual machine.",
1510 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1513 additionalProperties
=> 0,
1515 node
=> get_standard_option
('pve-node'),
1516 vmid
=> get_standard_option
('pve-vmid'),
1517 skiplock
=> get_standard_option
('skiplock'),
1519 description
=> "Wait maximal timeout seconds.",
1525 description
=> "Make sure the VM stops.",
1531 description
=> "Do not decativate storage volumes.",
1544 my $rpcenv = PVE
::RPCEnvironment
::get
();
1546 my $authuser = $rpcenv->get_user();
1548 my $node = extract_param
($param, 'node');
1550 my $vmid = extract_param
($param, 'vmid');
1552 my $skiplock = extract_param
($param, 'skiplock');
1553 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1554 if $skiplock && $authuser ne 'root@pam';
1556 my $keepActive = extract_param
($param, 'keepActive');
1557 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1558 if $keepActive && $authuser ne 'root@pam';
1560 my $storecfg = PVE
::Storage
::config
();
1565 syslog
('info', "shutdown VM $vmid: $upid\n");
1567 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1568 1, $param->{forceStop
}, $keepActive);
1573 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1576 __PACKAGE__-
>register_method({
1577 name
=> 'vm_suspend',
1578 path
=> '{vmid}/status/suspend',
1582 description
=> "Suspend virtual machine.",
1584 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1587 additionalProperties
=> 0,
1589 node
=> get_standard_option
('pve-node'),
1590 vmid
=> get_standard_option
('pve-vmid'),
1591 skiplock
=> get_standard_option
('skiplock'),
1600 my $rpcenv = PVE
::RPCEnvironment
::get
();
1602 my $authuser = $rpcenv->get_user();
1604 my $node = extract_param
($param, 'node');
1606 my $vmid = extract_param
($param, 'vmid');
1608 my $skiplock = extract_param
($param, 'skiplock');
1609 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1610 if $skiplock && $authuser ne 'root@pam';
1612 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1617 syslog
('info', "suspend VM $vmid: $upid\n");
1619 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1624 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1627 __PACKAGE__-
>register_method({
1628 name
=> 'vm_resume',
1629 path
=> '{vmid}/status/resume',
1633 description
=> "Resume virtual machine.",
1635 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1638 additionalProperties
=> 0,
1640 node
=> get_standard_option
('pve-node'),
1641 vmid
=> get_standard_option
('pve-vmid'),
1642 skiplock
=> get_standard_option
('skiplock'),
1651 my $rpcenv = PVE
::RPCEnvironment
::get
();
1653 my $authuser = $rpcenv->get_user();
1655 my $node = extract_param
($param, 'node');
1657 my $vmid = extract_param
($param, 'vmid');
1659 my $skiplock = extract_param
($param, 'skiplock');
1660 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1661 if $skiplock && $authuser ne 'root@pam';
1663 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1668 syslog
('info', "resume VM $vmid: $upid\n");
1670 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1675 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1678 __PACKAGE__-
>register_method({
1679 name
=> 'vm_sendkey',
1680 path
=> '{vmid}/sendkey',
1684 description
=> "Send key event to virtual machine.",
1686 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1689 additionalProperties
=> 0,
1691 node
=> get_standard_option
('pve-node'),
1692 vmid
=> get_standard_option
('pve-vmid'),
1693 skiplock
=> get_standard_option
('skiplock'),
1695 description
=> "The key (qemu monitor encoding).",
1700 returns
=> { type
=> 'null'},
1704 my $rpcenv = PVE
::RPCEnvironment
::get
();
1706 my $authuser = $rpcenv->get_user();
1708 my $node = extract_param
($param, 'node');
1710 my $vmid = extract_param
($param, 'vmid');
1712 my $skiplock = extract_param
($param, 'skiplock');
1713 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1714 if $skiplock && $authuser ne 'root@pam';
1716 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1721 __PACKAGE__-
>register_method({
1722 name
=> 'vm_feature',
1723 path
=> '{vmid}/feature',
1727 description
=> "Check if feature for virtual machine is available.",
1729 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1732 additionalProperties
=> 0,
1734 node
=> get_standard_option
('pve-node'),
1735 vmid
=> get_standard_option
('pve-vmid'),
1737 description
=> "Feature to check.",
1739 enum
=> [ 'snapshot', 'clone' ],
1741 snapname
=> get_standard_option
('pve-snapshot-name', {
1752 my $node = extract_param
($param, 'node');
1754 my $vmid = extract_param
($param, 'vmid');
1756 my $snapname = extract_param
($param, 'snapname');
1758 my $feature = extract_param
($param, 'feature');
1760 my $running = PVE
::QemuServer
::check_running
($vmid);
1762 my $conf = PVE
::QemuServer
::load_config
($vmid);
1765 my $snap = $conf->{snapshots
}->{$snapname};
1766 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1769 my $storecfg = PVE
::Storage
::config
();
1771 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1772 my $res = $hasfeature ?
1 : 0 ;
1776 __PACKAGE__-
>register_method({
1778 path
=> '{vmid}/copy',
1782 description
=> "Create a copy of virtual machine/template.",
1784 description
=> "You need 'VM.Copy' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1785 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1786 "'Datastore.AllocateSpace' on any used storage.",
1789 ['perm', '/vms/{vmid}', [ 'VM.Copy' ]],
1791 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1792 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1797 additionalProperties
=> 0,
1799 node
=> get_standard_option
('pve-node'),
1800 vmid
=> get_standard_option
('pve-vmid'),
1801 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the copy.' }),
1804 type
=> 'string', format
=> 'dns-name',
1805 description
=> "Set a name for the new VM.",
1810 description
=> "Description for the new VM.",
1814 type
=> 'string', format
=> 'pve-poolid',
1815 description
=> "Add the new VM to the specified pool.",
1817 snapname
=> get_standard_option
('pve-snapshot-name', {
1821 storage
=> get_standard_option
('pve-storage-id', {
1822 description
=> "Target storage for full copy.",
1829 description
=> "Create a full copy of all disk. This is always done when " .
1830 "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
1841 my $rpcenv = PVE
::RPCEnvironment
::get
();
1843 my $authuser = $rpcenv->get_user();
1845 my $node = extract_param
($param, 'node');
1847 my $vmid = extract_param
($param, 'vmid');
1849 my $newid = extract_param
($param, 'newid');
1851 # fixme: update pool after create
1852 my $pool = extract_param
($param, 'pool');
1854 if (defined($pool)) {
1855 $rpcenv->check_pool_exist($pool);
1858 my $snapname = extract_param
($param, 'snapname');
1860 my $storage = extract_param
($param, 'storage');
1862 my $storecfg = PVE
::Storage
::config
();
1864 PVE
::Cluster
::check_cfs_quorum
();
1866 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1868 die "Copy running VM $vmid not implemented\n" if $running; # fixme: implement this
1870 # exclusive lock if VM is running - else shared lock is enough;
1871 my $shared_lock = $running ?
0 : 1;
1873 # fixme: do early checks - re-check after lock
1875 # fixme: impl. target node parameter (mv VM config if all storages are shared)
1879 # all tests after lock
1880 my $conf = PVE
::QemuServer
::load_config
($vmid);
1882 PVE
::QemuServer
::check_lock
($conf);
1884 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1886 die "unexpected state change\n" if $verify_running != $running;
1888 die "snapshot '$snapname' does not exist\n"
1889 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1891 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1893 &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1895 my $conffile = PVE
::QemuServer
::config_file
($newid);
1897 die "unable to create VM $newid: config file already exists\n"
1900 # create empty/temp config - this fails if VM already exists on other node
1901 PVE
::Tools
::file_set_contents
($conffile, "# qmcopy temporary file\nlock: copy\n");
1906 my $newvollist = [];
1909 my $newconf = { lock => 'copy' };
1913 foreach my $opt (keys %$oldconf) {
1914 my $value = $oldconf->{$opt};
1916 # do not copy snapshot related info
1917 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1918 $opt eq 'vmstate' || $opt eq 'snapstate';
1920 # always change MAC! address
1921 if ($opt =~ m/^net(\d+)$/) {
1922 my $net = PVE
::QemuServer
::parse_net
($value);
1923 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1924 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1925 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1926 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1927 $newconf->{$opt} = $value; # simply copy configuration
1929 $drives->{$opt} = $drive;
1930 push @$vollist, $drive->{file
};
1933 # copy everything else
1934 $newconf->{$opt} = $value;
1938 delete $newconf->{template
};
1940 if ($param->{name
}) {
1941 $newconf->{name
} = $param->{name
};
1943 $newconf->{name
} = "Copy-of-$oldconf->{name}";
1946 if ($param->{description
}) {
1947 $newconf->{description
} = $param->{description
};
1950 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
1953 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1955 foreach my $opt (keys %$drives) {
1956 my $drive = $drives->{$opt};
1959 if (!$param->{full
} && PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1960 print "clone drive $opt ($drive->{file})\n";
1961 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
1963 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
1964 $storeid = $storage if $storage;
1965 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1966 my $fmt = $drive->{format
} || $defformat;
1968 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
1970 print "copy drive $opt ($drive->{file})\n";
1971 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
1973 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
1976 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
1977 my $disk = { file
=> $newvolid, size
=> $size };
1978 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
1979 push @$newvollist, $newvolid;
1981 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
1986 delete $newconf->{lock};
1987 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
1992 sleep 1; # some storage like rbd need to wait before release volume - really?
1994 foreach my $volid (@$newvollist) {
1995 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1998 die "copy failed: $err";
2004 return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
2007 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2008 # Aquire exclusive lock lock for $newid
2009 return PVE
::QemuServer
::lock_config_full
($newid, 1, $copyfn);
2014 __PACKAGE__-
>register_method({
2015 name
=> 'migrate_vm',
2016 path
=> '{vmid}/migrate',
2020 description
=> "Migrate virtual machine. Creates a new migration task.",
2022 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2025 additionalProperties
=> 0,
2027 node
=> get_standard_option
('pve-node'),
2028 vmid
=> get_standard_option
('pve-vmid'),
2029 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2032 description
=> "Use online/live migration.",
2037 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2044 description
=> "the task ID.",
2049 my $rpcenv = PVE
::RPCEnvironment
::get
();
2051 my $authuser = $rpcenv->get_user();
2053 my $target = extract_param
($param, 'target');
2055 my $localnode = PVE
::INotify
::nodename
();
2056 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2058 PVE
::Cluster
::check_cfs_quorum
();
2060 PVE
::Cluster
::check_node_exists
($target);
2062 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2064 my $vmid = extract_param
($param, 'vmid');
2066 raise_param_exc
({ force
=> "Only root may use this option." })
2067 if $param->{force
} && $authuser ne 'root@pam';
2070 my $conf = PVE
::QemuServer
::load_config
($vmid);
2072 # try to detect errors early
2074 PVE
::QemuServer
::check_lock
($conf);
2076 if (PVE
::QemuServer
::check_running
($vmid)) {
2077 die "cant migrate running VM without --online\n"
2078 if !$param->{online
};
2081 my $storecfg = PVE
::Storage
::config
();
2082 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2084 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2089 my $service = "pvevm:$vmid";
2091 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2093 print "Executing HA migrate for VM $vmid to node $target\n";
2095 PVE
::Tools
::run_command
($cmd);
2100 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2107 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2110 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2115 __PACKAGE__-
>register_method({
2117 path
=> '{vmid}/monitor',
2121 description
=> "Execute Qemu monitor commands.",
2123 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2126 additionalProperties
=> 0,
2128 node
=> get_standard_option
('pve-node'),
2129 vmid
=> get_standard_option
('pve-vmid'),
2132 description
=> "The monitor command.",
2136 returns
=> { type
=> 'string'},
2140 my $vmid = $param->{vmid
};
2142 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2146 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2148 $res = "ERROR: $@" if $@;
2153 __PACKAGE__-
>register_method({
2154 name
=> 'resize_vm',
2155 path
=> '{vmid}/resize',
2159 description
=> "Extend volume size.",
2161 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2164 additionalProperties
=> 0,
2166 node
=> get_standard_option
('pve-node'),
2167 vmid
=> get_standard_option
('pve-vmid'),
2168 skiplock
=> get_standard_option
('skiplock'),
2171 description
=> "The disk you want to resize.",
2172 enum
=> [PVE
::QemuServer
::disknames
()],
2176 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2177 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.",
2181 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2187 returns
=> { type
=> 'null'},
2191 my $rpcenv = PVE
::RPCEnvironment
::get
();
2193 my $authuser = $rpcenv->get_user();
2195 my $node = extract_param
($param, 'node');
2197 my $vmid = extract_param
($param, 'vmid');
2199 my $digest = extract_param
($param, 'digest');
2201 my $disk = extract_param
($param, 'disk');
2203 my $sizestr = extract_param
($param, 'size');
2205 my $skiplock = extract_param
($param, 'skiplock');
2206 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2207 if $skiplock && $authuser ne 'root@pam';
2209 my $storecfg = PVE
::Storage
::config
();
2211 my $updatefn = sub {
2213 my $conf = PVE
::QemuServer
::load_config
($vmid);
2215 die "checksum missmatch (file change by other user?)\n"
2216 if $digest && $digest ne $conf->{digest
};
2217 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2219 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2221 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2223 my $volid = $drive->{file
};
2225 die "disk '$disk' has no associated volume\n" if !$volid;
2227 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2229 die "you can't online resize a virtio windows bootdisk\n"
2230 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2232 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2234 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2236 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2238 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2239 my ($ext, $newsize, $unit) = ($1, $2, $4);
2242 $newsize = $newsize * 1024;
2243 } elsif ($unit eq 'M') {
2244 $newsize = $newsize * 1024 * 1024;
2245 } elsif ($unit eq 'G') {
2246 $newsize = $newsize * 1024 * 1024 * 1024;
2247 } elsif ($unit eq 'T') {
2248 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2251 $newsize += $size if $ext;
2252 $newsize = int($newsize);
2254 die "unable to skrink disk size\n" if $newsize < $size;
2256 return if $size == $newsize;
2258 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2260 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2262 $drive->{size
} = $newsize;
2263 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2265 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2268 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2272 __PACKAGE__-
>register_method({
2273 name
=> 'snapshot_list',
2274 path
=> '{vmid}/snapshot',
2276 description
=> "List all snapshots.",
2278 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2281 protected
=> 1, # qemu pid files are only readable by root
2283 additionalProperties
=> 0,
2285 vmid
=> get_standard_option
('pve-vmid'),
2286 node
=> get_standard_option
('pve-node'),
2295 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2300 my $vmid = $param->{vmid
};
2302 my $conf = PVE
::QemuServer
::load_config
($vmid);
2303 my $snaphash = $conf->{snapshots
} || {};
2307 foreach my $name (keys %$snaphash) {
2308 my $d = $snaphash->{$name};
2311 snaptime
=> $d->{snaptime
} || 0,
2312 vmstate
=> $d->{vmstate
} ?
1 : 0,
2313 description
=> $d->{description
} || '',
2315 $item->{parent
} = $d->{parent
} if $d->{parent
};
2316 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2320 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2321 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2322 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2324 push @$res, $current;
2329 __PACKAGE__-
>register_method({
2331 path
=> '{vmid}/snapshot',
2335 description
=> "Snapshot a VM.",
2337 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2340 additionalProperties
=> 0,
2342 node
=> get_standard_option
('pve-node'),
2343 vmid
=> get_standard_option
('pve-vmid'),
2344 snapname
=> get_standard_option
('pve-snapshot-name'),
2348 description
=> "Save the vmstate",
2353 description
=> "Freeze the filesystem",
2358 description
=> "A textual description or comment.",
2364 description
=> "the task ID.",
2369 my $rpcenv = PVE
::RPCEnvironment
::get
();
2371 my $authuser = $rpcenv->get_user();
2373 my $node = extract_param
($param, 'node');
2375 my $vmid = extract_param
($param, 'vmid');
2377 my $snapname = extract_param
($param, 'snapname');
2379 die "unable to use snapshot name 'current' (reserved name)\n"
2380 if $snapname eq 'current';
2383 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2384 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2385 $param->{freezefs
}, $param->{description
});
2388 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2391 __PACKAGE__-
>register_method({
2392 name
=> 'snapshot_cmd_idx',
2393 path
=> '{vmid}/snapshot/{snapname}',
2400 additionalProperties
=> 0,
2402 vmid
=> get_standard_option
('pve-vmid'),
2403 node
=> get_standard_option
('pve-node'),
2404 snapname
=> get_standard_option
('pve-snapshot-name'),
2413 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2420 push @$res, { cmd
=> 'rollback' };
2421 push @$res, { cmd
=> 'config' };
2426 __PACKAGE__-
>register_method({
2427 name
=> 'update_snapshot_config',
2428 path
=> '{vmid}/snapshot/{snapname}/config',
2432 description
=> "Update snapshot metadata.",
2434 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2437 additionalProperties
=> 0,
2439 node
=> get_standard_option
('pve-node'),
2440 vmid
=> get_standard_option
('pve-vmid'),
2441 snapname
=> get_standard_option
('pve-snapshot-name'),
2445 description
=> "A textual description or comment.",
2449 returns
=> { type
=> 'null' },
2453 my $rpcenv = PVE
::RPCEnvironment
::get
();
2455 my $authuser = $rpcenv->get_user();
2457 my $vmid = extract_param
($param, 'vmid');
2459 my $snapname = extract_param
($param, 'snapname');
2461 return undef if !defined($param->{description
});
2463 my $updatefn = sub {
2465 my $conf = PVE
::QemuServer
::load_config
($vmid);
2467 PVE
::QemuServer
::check_lock
($conf);
2469 my $snap = $conf->{snapshots
}->{$snapname};
2471 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2473 $snap->{description
} = $param->{description
} if defined($param->{description
});
2475 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2478 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2483 __PACKAGE__-
>register_method({
2484 name
=> 'get_snapshot_config',
2485 path
=> '{vmid}/snapshot/{snapname}/config',
2488 description
=> "Get snapshot configuration",
2490 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2493 additionalProperties
=> 0,
2495 node
=> get_standard_option
('pve-node'),
2496 vmid
=> get_standard_option
('pve-vmid'),
2497 snapname
=> get_standard_option
('pve-snapshot-name'),
2500 returns
=> { type
=> "object" },
2504 my $rpcenv = PVE
::RPCEnvironment
::get
();
2506 my $authuser = $rpcenv->get_user();
2508 my $vmid = extract_param
($param, 'vmid');
2510 my $snapname = extract_param
($param, 'snapname');
2512 my $conf = PVE
::QemuServer
::load_config
($vmid);
2514 my $snap = $conf->{snapshots
}->{$snapname};
2516 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2521 __PACKAGE__-
>register_method({
2523 path
=> '{vmid}/snapshot/{snapname}/rollback',
2527 description
=> "Rollback VM state to specified snapshot.",
2529 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2532 additionalProperties
=> 0,
2534 node
=> get_standard_option
('pve-node'),
2535 vmid
=> get_standard_option
('pve-vmid'),
2536 snapname
=> get_standard_option
('pve-snapshot-name'),
2541 description
=> "the task ID.",
2546 my $rpcenv = PVE
::RPCEnvironment
::get
();
2548 my $authuser = $rpcenv->get_user();
2550 my $node = extract_param
($param, 'node');
2552 my $vmid = extract_param
($param, 'vmid');
2554 my $snapname = extract_param
($param, 'snapname');
2557 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2558 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2561 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2564 __PACKAGE__-
>register_method({
2565 name
=> 'delsnapshot',
2566 path
=> '{vmid}/snapshot/{snapname}',
2570 description
=> "Delete a VM snapshot.",
2572 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2575 additionalProperties
=> 0,
2577 node
=> get_standard_option
('pve-node'),
2578 vmid
=> get_standard_option
('pve-vmid'),
2579 snapname
=> get_standard_option
('pve-snapshot-name'),
2583 description
=> "For removal from config file, even if removing disk snapshots fails.",
2589 description
=> "the task ID.",
2594 my $rpcenv = PVE
::RPCEnvironment
::get
();
2596 my $authuser = $rpcenv->get_user();
2598 my $node = extract_param
($param, 'node');
2600 my $vmid = extract_param
($param, 'vmid');
2602 my $snapname = extract_param
($param, 'snapname');
2605 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2606 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2609 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2612 __PACKAGE__-
>register_method({
2614 path
=> '{vmid}/template',
2618 description
=> "Create a Template.",
2620 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
2622 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2623 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2627 additionalProperties
=> 0,
2629 node
=> get_standard_option
('pve-node'),
2630 vmid
=> get_standard_option
('pve-vmid'),
2634 description
=> "If you want to convert only 1 disk to base image.",
2635 enum
=> [PVE
::QemuServer
::disknames
()],
2640 returns
=> { type
=> 'null'},
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 $disk = extract_param
($param, 'disk');
2654 my $updatefn = sub {
2656 my $conf = PVE
::QemuServer
::load_config
($vmid);
2658 PVE
::QemuServer
::check_lock
($conf);
2660 die "unable to create template, because VM contains snapshots\n"
2661 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2663 die "you can't convert a template to a template\n"
2664 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2666 die "you can't convert a VM to template if VM is running\n"
2667 if PVE
::QemuServer
::check_running
($vmid);
2670 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2673 $conf->{template
} = 1;
2674 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2676 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2679 PVE
::QemuServer
::lock_config
($vmid, $updatefn);