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.",
1827 description
=> "Target format for file storage.",
1831 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1836 description
=> "Create a full copy of all disk. This is always done when " .
1837 "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
1848 my $rpcenv = PVE
::RPCEnvironment
::get
();
1850 my $authuser = $rpcenv->get_user();
1852 my $node = extract_param
($param, 'node');
1854 my $vmid = extract_param
($param, 'vmid');
1856 my $newid = extract_param
($param, 'newid');
1858 # fixme: update pool after create
1859 my $pool = extract_param
($param, 'pool');
1861 if (defined($pool)) {
1862 $rpcenv->check_pool_exist($pool);
1865 my $snapname = extract_param
($param, 'snapname');
1867 my $storage = extract_param
($param, 'storage');
1869 my $format = extract_param
($param, 'format');
1871 my $storecfg = PVE
::Storage
::config
();
1873 PVE
::Cluster
::check_cfs_quorum
();
1875 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1877 die "Copy running VM $vmid not implemented\n" if $running; # fixme: implement this
1879 # exclusive lock if VM is running - else shared lock is enough;
1880 my $shared_lock = $running ?
0 : 1;
1882 # fixme: do early checks - re-check after lock
1884 # fixme: impl. target node parameter (mv VM config if all storages are shared)
1888 # all tests after lock
1889 my $conf = PVE
::QemuServer
::load_config
($vmid);
1891 PVE
::QemuServer
::check_lock
($conf);
1893 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1895 die "unexpected state change\n" if $verify_running != $running;
1897 die "snapshot '$snapname' does not exist\n"
1898 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1900 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1902 &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1904 my $conffile = PVE
::QemuServer
::config_file
($newid);
1906 die "unable to create VM $newid: config file already exists\n"
1909 # create empty/temp config - this fails if VM already exists on other node
1910 PVE
::Tools
::file_set_contents
($conffile, "# qmcopy temporary file\nlock: copy\n");
1915 my $newvollist = [];
1918 my $newconf = { lock => 'copy' };
1922 foreach my $opt (keys %$oldconf) {
1923 my $value = $oldconf->{$opt};
1925 # do not copy snapshot related info
1926 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1927 $opt eq 'vmstate' || $opt eq 'snapstate';
1929 # always change MAC! address
1930 if ($opt =~ m/^net(\d+)$/) {
1931 my $net = PVE
::QemuServer
::parse_net
($value);
1932 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1933 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1934 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1935 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1936 $newconf->{$opt} = $value; # simply copy configuration
1938 $drives->{$opt} = $drive;
1939 push @$vollist, $drive->{file
};
1942 # copy everything else
1943 $newconf->{$opt} = $value;
1947 delete $newconf->{template
};
1949 if ($param->{name
}) {
1950 $newconf->{name
} = $param->{name
};
1952 $newconf->{name
} = "Copy-of-$oldconf->{name}";
1955 if ($param->{description
}) {
1956 $newconf->{description
} = $param->{description
};
1959 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
1962 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1964 foreach my $opt (keys %$drives) {
1965 my $drive = $drives->{$opt};
1968 if (!$param->{full
} && PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1969 print "clone drive $opt ($drive->{file})\n";
1970 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
1972 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
1973 $storeid = $storage if $storage;
1979 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1980 $fmt = $drive->{format
} || $defformat;
1983 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
1985 print "copy drive $opt ($drive->{file})\n";
1986 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
1988 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
1991 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
1992 my $disk = { file
=> $newvolid, size
=> $size };
1993 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
1994 push @$newvollist, $newvolid;
1996 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2001 delete $newconf->{lock};
2002 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2007 sleep 1; # some storage like rbd need to wait before release volume - really?
2009 foreach my $volid (@$newvollist) {
2010 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2013 die "copy failed: $err";
2019 return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
2022 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2023 # Aquire exclusive lock lock for $newid
2024 return PVE
::QemuServer
::lock_config_full
($newid, 1, $copyfn);
2029 __PACKAGE__-
>register_method({
2030 name
=> 'migrate_vm',
2031 path
=> '{vmid}/migrate',
2035 description
=> "Migrate virtual machine. Creates a new migration task.",
2037 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2040 additionalProperties
=> 0,
2042 node
=> get_standard_option
('pve-node'),
2043 vmid
=> get_standard_option
('pve-vmid'),
2044 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2047 description
=> "Use online/live migration.",
2052 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2059 description
=> "the task ID.",
2064 my $rpcenv = PVE
::RPCEnvironment
::get
();
2066 my $authuser = $rpcenv->get_user();
2068 my $target = extract_param
($param, 'target');
2070 my $localnode = PVE
::INotify
::nodename
();
2071 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2073 PVE
::Cluster
::check_cfs_quorum
();
2075 PVE
::Cluster
::check_node_exists
($target);
2077 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2079 my $vmid = extract_param
($param, 'vmid');
2081 raise_param_exc
({ force
=> "Only root may use this option." })
2082 if $param->{force
} && $authuser ne 'root@pam';
2085 my $conf = PVE
::QemuServer
::load_config
($vmid);
2087 # try to detect errors early
2089 PVE
::QemuServer
::check_lock
($conf);
2091 if (PVE
::QemuServer
::check_running
($vmid)) {
2092 die "cant migrate running VM without --online\n"
2093 if !$param->{online
};
2096 my $storecfg = PVE
::Storage
::config
();
2097 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2099 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2104 my $service = "pvevm:$vmid";
2106 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2108 print "Executing HA migrate for VM $vmid to node $target\n";
2110 PVE
::Tools
::run_command
($cmd);
2115 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2122 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2125 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2130 __PACKAGE__-
>register_method({
2132 path
=> '{vmid}/monitor',
2136 description
=> "Execute Qemu monitor commands.",
2138 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2141 additionalProperties
=> 0,
2143 node
=> get_standard_option
('pve-node'),
2144 vmid
=> get_standard_option
('pve-vmid'),
2147 description
=> "The monitor command.",
2151 returns
=> { type
=> 'string'},
2155 my $vmid = $param->{vmid
};
2157 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2161 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2163 $res = "ERROR: $@" if $@;
2168 __PACKAGE__-
>register_method({
2169 name
=> 'resize_vm',
2170 path
=> '{vmid}/resize',
2174 description
=> "Extend volume size.",
2176 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2179 additionalProperties
=> 0,
2181 node
=> get_standard_option
('pve-node'),
2182 vmid
=> get_standard_option
('pve-vmid'),
2183 skiplock
=> get_standard_option
('skiplock'),
2186 description
=> "The disk you want to resize.",
2187 enum
=> [PVE
::QemuServer
::disknames
()],
2191 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2192 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.",
2196 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2202 returns
=> { type
=> 'null'},
2206 my $rpcenv = PVE
::RPCEnvironment
::get
();
2208 my $authuser = $rpcenv->get_user();
2210 my $node = extract_param
($param, 'node');
2212 my $vmid = extract_param
($param, 'vmid');
2214 my $digest = extract_param
($param, 'digest');
2216 my $disk = extract_param
($param, 'disk');
2218 my $sizestr = extract_param
($param, 'size');
2220 my $skiplock = extract_param
($param, 'skiplock');
2221 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2222 if $skiplock && $authuser ne 'root@pam';
2224 my $storecfg = PVE
::Storage
::config
();
2226 my $updatefn = sub {
2228 my $conf = PVE
::QemuServer
::load_config
($vmid);
2230 die "checksum missmatch (file change by other user?)\n"
2231 if $digest && $digest ne $conf->{digest
};
2232 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2234 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2236 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2238 my $volid = $drive->{file
};
2240 die "disk '$disk' has no associated volume\n" if !$volid;
2242 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2244 die "you can't online resize a virtio windows bootdisk\n"
2245 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2247 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2249 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2251 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2253 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2254 my ($ext, $newsize, $unit) = ($1, $2, $4);
2257 $newsize = $newsize * 1024;
2258 } elsif ($unit eq 'M') {
2259 $newsize = $newsize * 1024 * 1024;
2260 } elsif ($unit eq 'G') {
2261 $newsize = $newsize * 1024 * 1024 * 1024;
2262 } elsif ($unit eq 'T') {
2263 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2266 $newsize += $size if $ext;
2267 $newsize = int($newsize);
2269 die "unable to skrink disk size\n" if $newsize < $size;
2271 return if $size == $newsize;
2273 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2275 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2277 $drive->{size
} = $newsize;
2278 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2280 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2283 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2287 __PACKAGE__-
>register_method({
2288 name
=> 'snapshot_list',
2289 path
=> '{vmid}/snapshot',
2291 description
=> "List all snapshots.",
2293 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2296 protected
=> 1, # qemu pid files are only readable by root
2298 additionalProperties
=> 0,
2300 vmid
=> get_standard_option
('pve-vmid'),
2301 node
=> get_standard_option
('pve-node'),
2310 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2315 my $vmid = $param->{vmid
};
2317 my $conf = PVE
::QemuServer
::load_config
($vmid);
2318 my $snaphash = $conf->{snapshots
} || {};
2322 foreach my $name (keys %$snaphash) {
2323 my $d = $snaphash->{$name};
2326 snaptime
=> $d->{snaptime
} || 0,
2327 vmstate
=> $d->{vmstate
} ?
1 : 0,
2328 description
=> $d->{description
} || '',
2330 $item->{parent
} = $d->{parent
} if $d->{parent
};
2331 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2335 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2336 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2337 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2339 push @$res, $current;
2344 __PACKAGE__-
>register_method({
2346 path
=> '{vmid}/snapshot',
2350 description
=> "Snapshot a VM.",
2352 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2355 additionalProperties
=> 0,
2357 node
=> get_standard_option
('pve-node'),
2358 vmid
=> get_standard_option
('pve-vmid'),
2359 snapname
=> get_standard_option
('pve-snapshot-name'),
2363 description
=> "Save the vmstate",
2368 description
=> "Freeze the filesystem",
2373 description
=> "A textual description or comment.",
2379 description
=> "the task ID.",
2384 my $rpcenv = PVE
::RPCEnvironment
::get
();
2386 my $authuser = $rpcenv->get_user();
2388 my $node = extract_param
($param, 'node');
2390 my $vmid = extract_param
($param, 'vmid');
2392 my $snapname = extract_param
($param, 'snapname');
2394 die "unable to use snapshot name 'current' (reserved name)\n"
2395 if $snapname eq 'current';
2398 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2399 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2400 $param->{freezefs
}, $param->{description
});
2403 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2406 __PACKAGE__-
>register_method({
2407 name
=> 'snapshot_cmd_idx',
2408 path
=> '{vmid}/snapshot/{snapname}',
2415 additionalProperties
=> 0,
2417 vmid
=> get_standard_option
('pve-vmid'),
2418 node
=> get_standard_option
('pve-node'),
2419 snapname
=> get_standard_option
('pve-snapshot-name'),
2428 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2435 push @$res, { cmd
=> 'rollback' };
2436 push @$res, { cmd
=> 'config' };
2441 __PACKAGE__-
>register_method({
2442 name
=> 'update_snapshot_config',
2443 path
=> '{vmid}/snapshot/{snapname}/config',
2447 description
=> "Update snapshot metadata.",
2449 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2452 additionalProperties
=> 0,
2454 node
=> get_standard_option
('pve-node'),
2455 vmid
=> get_standard_option
('pve-vmid'),
2456 snapname
=> get_standard_option
('pve-snapshot-name'),
2460 description
=> "A textual description or comment.",
2464 returns
=> { type
=> 'null' },
2468 my $rpcenv = PVE
::RPCEnvironment
::get
();
2470 my $authuser = $rpcenv->get_user();
2472 my $vmid = extract_param
($param, 'vmid');
2474 my $snapname = extract_param
($param, 'snapname');
2476 return undef if !defined($param->{description
});
2478 my $updatefn = sub {
2480 my $conf = PVE
::QemuServer
::load_config
($vmid);
2482 PVE
::QemuServer
::check_lock
($conf);
2484 my $snap = $conf->{snapshots
}->{$snapname};
2486 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2488 $snap->{description
} = $param->{description
} if defined($param->{description
});
2490 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2493 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2498 __PACKAGE__-
>register_method({
2499 name
=> 'get_snapshot_config',
2500 path
=> '{vmid}/snapshot/{snapname}/config',
2503 description
=> "Get snapshot configuration",
2505 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2508 additionalProperties
=> 0,
2510 node
=> get_standard_option
('pve-node'),
2511 vmid
=> get_standard_option
('pve-vmid'),
2512 snapname
=> get_standard_option
('pve-snapshot-name'),
2515 returns
=> { type
=> "object" },
2519 my $rpcenv = PVE
::RPCEnvironment
::get
();
2521 my $authuser = $rpcenv->get_user();
2523 my $vmid = extract_param
($param, 'vmid');
2525 my $snapname = extract_param
($param, 'snapname');
2527 my $conf = PVE
::QemuServer
::load_config
($vmid);
2529 my $snap = $conf->{snapshots
}->{$snapname};
2531 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2536 __PACKAGE__-
>register_method({
2538 path
=> '{vmid}/snapshot/{snapname}/rollback',
2542 description
=> "Rollback VM state to specified snapshot.",
2544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2547 additionalProperties
=> 0,
2549 node
=> get_standard_option
('pve-node'),
2550 vmid
=> get_standard_option
('pve-vmid'),
2551 snapname
=> get_standard_option
('pve-snapshot-name'),
2556 description
=> "the task ID.",
2561 my $rpcenv = PVE
::RPCEnvironment
::get
();
2563 my $authuser = $rpcenv->get_user();
2565 my $node = extract_param
($param, 'node');
2567 my $vmid = extract_param
($param, 'vmid');
2569 my $snapname = extract_param
($param, 'snapname');
2572 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2573 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2576 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2579 __PACKAGE__-
>register_method({
2580 name
=> 'delsnapshot',
2581 path
=> '{vmid}/snapshot/{snapname}',
2585 description
=> "Delete a VM snapshot.",
2587 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2590 additionalProperties
=> 0,
2592 node
=> get_standard_option
('pve-node'),
2593 vmid
=> get_standard_option
('pve-vmid'),
2594 snapname
=> get_standard_option
('pve-snapshot-name'),
2598 description
=> "For removal from config file, even if removing disk snapshots fails.",
2604 description
=> "the task ID.",
2609 my $rpcenv = PVE
::RPCEnvironment
::get
();
2611 my $authuser = $rpcenv->get_user();
2613 my $node = extract_param
($param, 'node');
2615 my $vmid = extract_param
($param, 'vmid');
2617 my $snapname = extract_param
($param, 'snapname');
2620 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2621 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2624 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2627 __PACKAGE__-
>register_method({
2629 path
=> '{vmid}/template',
2633 description
=> "Create a Template.",
2635 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
2637 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2638 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2642 additionalProperties
=> 0,
2644 node
=> get_standard_option
('pve-node'),
2645 vmid
=> get_standard_option
('pve-vmid'),
2649 description
=> "If you want to convert only 1 disk to base image.",
2650 enum
=> [PVE
::QemuServer
::disknames
()],
2655 returns
=> { type
=> 'null'},
2659 my $rpcenv = PVE
::RPCEnvironment
::get
();
2661 my $authuser = $rpcenv->get_user();
2663 my $node = extract_param
($param, 'node');
2665 my $vmid = extract_param
($param, 'vmid');
2667 my $disk = extract_param
($param, 'disk');
2669 my $updatefn = sub {
2671 my $conf = PVE
::QemuServer
::load_config
($vmid);
2673 PVE
::QemuServer
::check_lock
($conf);
2675 die "unable to create template, because VM contains snapshots\n"
2676 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2678 die "you can't convert a template to a template\n"
2679 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2681 die "you can't convert a VM to template if VM is running\n"
2682 if PVE
::QemuServer
::check_running
($vmid);
2685 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2688 $conf->{template
} = 1;
2689 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2691 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2694 PVE
::QemuServer
::lock_config
($vmid, $updatefn);