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 # fixme: add other parameters like name and description?
1800 node
=> get_standard_option
('pve-node'),
1801 vmid
=> get_standard_option
('pve-vmid'),
1802 newid
=> get_standard_option
('pve-vmid', {
1803 description
=> 'VMID for the copy.' }),
1806 type
=> 'string', format
=> 'pve-poolid',
1807 description
=> "Add the new VM to the specified pool.",
1809 snapname
=> get_standard_option
('pve-snapshot-name', {
1813 storage
=> get_standard_option
('pve-storage-id', {
1814 description
=> "Target storage for full copy.",
1821 description
=> "Create a full copy of all disk. This is always done when " .
1822 "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
1833 my $rpcenv = PVE
::RPCEnvironment
::get
();
1835 my $authuser = $rpcenv->get_user();
1837 my $node = extract_param
($param, 'node');
1839 my $vmid = extract_param
($param, 'vmid');
1841 my $newid = extract_param
($param, 'newid');
1843 # fixme: update pool after create
1844 my $pool = extract_param
($param, 'pool');
1846 if (defined($pool)) {
1847 $rpcenv->check_pool_exist($pool);
1850 my $snapname = extract_param
($param, 'snapname');
1852 my $storage = extract_param
($param, 'storage');
1854 my $storecfg = PVE
::Storage
::config
();
1856 PVE
::Cluster
::check_cfs_quorum
();
1858 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1860 die "Copy running VM $vmid not implemented\n" if $running; # fixme: implement this
1862 # exclusive lock if VM is running - else shared lock is enough;
1863 my $shared_lock = $running ?
0 : 1;
1865 # fixme: do early checks - re-check after lock
1867 # fixme: impl. target node parameter (mv VM config if all storages are shared)
1871 # all tests after lock
1872 my $conf = PVE
::QemuServer
::load_config
($vmid);
1874 PVE
::QemuServer
::check_lock
($conf);
1876 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1878 die "unexpected state change\n" if $verify_running != $running;
1880 die "snapshot '$snapname' does not exist\n"
1881 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1883 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1885 &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1887 my $conffile = PVE
::QemuServer
::config_file
($newid);
1889 die "unable to create VM $newid: config file already exists\n"
1892 # create empty/temp config - this fails if VM already exists on other node
1893 PVE
::Tools
::file_set_contents
($conffile, "# qmcopy temporary file\nlock: copy\n");
1898 my $newvollist = [];
1901 my $newconf = { lock => 'copy' };
1905 foreach my $opt (keys %$oldconf) {
1906 my $value = $oldconf->{$opt};
1908 # do not copy snapshot related info
1909 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1910 $opt eq 'vmstate' || $opt eq 'snapstate';
1912 # always change MAC! address
1913 if ($opt =~ m/^net(\d+)$/) {
1914 my $net = PVE
::QemuServer
::parse_net
($value);
1915 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1916 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1917 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1918 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1919 $newconf->{$opt} = $value; # simply copy configuration
1921 $drives->{$opt} = $drive;
1922 push @$vollist, $drive->{file
};
1925 # copy everything else
1926 $newconf->{$opt} = $value;
1930 delete $newconf->{template
};
1932 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
1935 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1937 foreach my $opt (keys %$drives) {
1938 my $drive = $drives->{$opt};
1941 if (!$param->{full
} && PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1942 print "clone drive $opt ($drive->{file})\n";
1943 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
1945 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
1946 $storeid = $storage if $storage;
1947 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1948 my $fmt = $drive->{format
} || $defformat;
1950 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
1952 print "copy drive $opt ($drive->{file})\n";
1953 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
1955 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
1958 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
1959 my $disk = { file
=> $newvolid, size
=> $size };
1960 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
1961 push @$newvollist, $newvolid;
1963 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
1968 delete $newconf->{lock};
1969 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
1974 sleep 1; # some storage like rbd need to wait before release volume - really?
1976 foreach my $volid (@$newvollist) {
1977 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1980 die "copy failed: $err";
1986 return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
1989 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
1990 # Aquire exclusive lock lock for $newid
1991 return PVE
::QemuServer
::lock_config_full
($newid, 1, $copyfn);
1996 __PACKAGE__-
>register_method({
1997 name
=> 'migrate_vm',
1998 path
=> '{vmid}/migrate',
2002 description
=> "Migrate virtual machine. Creates a new migration task.",
2004 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2007 additionalProperties
=> 0,
2009 node
=> get_standard_option
('pve-node'),
2010 vmid
=> get_standard_option
('pve-vmid'),
2011 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2014 description
=> "Use online/live migration.",
2019 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2026 description
=> "the task ID.",
2031 my $rpcenv = PVE
::RPCEnvironment
::get
();
2033 my $authuser = $rpcenv->get_user();
2035 my $target = extract_param
($param, 'target');
2037 my $localnode = PVE
::INotify
::nodename
();
2038 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2040 PVE
::Cluster
::check_cfs_quorum
();
2042 PVE
::Cluster
::check_node_exists
($target);
2044 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2046 my $vmid = extract_param
($param, 'vmid');
2048 raise_param_exc
({ force
=> "Only root may use this option." })
2049 if $param->{force
} && $authuser ne 'root@pam';
2052 my $conf = PVE
::QemuServer
::load_config
($vmid);
2054 # try to detect errors early
2056 PVE
::QemuServer
::check_lock
($conf);
2058 if (PVE
::QemuServer
::check_running
($vmid)) {
2059 die "cant migrate running VM without --online\n"
2060 if !$param->{online
};
2063 my $storecfg = PVE
::Storage
::config
();
2064 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2066 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2071 my $service = "pvevm:$vmid";
2073 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2075 print "Executing HA migrate for VM $vmid to node $target\n";
2077 PVE
::Tools
::run_command
($cmd);
2082 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2089 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2092 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2097 __PACKAGE__-
>register_method({
2099 path
=> '{vmid}/monitor',
2103 description
=> "Execute Qemu monitor commands.",
2105 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2108 additionalProperties
=> 0,
2110 node
=> get_standard_option
('pve-node'),
2111 vmid
=> get_standard_option
('pve-vmid'),
2114 description
=> "The monitor command.",
2118 returns
=> { type
=> 'string'},
2122 my $vmid = $param->{vmid
};
2124 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2128 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2130 $res = "ERROR: $@" if $@;
2135 __PACKAGE__-
>register_method({
2136 name
=> 'resize_vm',
2137 path
=> '{vmid}/resize',
2141 description
=> "Extend volume size.",
2143 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2146 additionalProperties
=> 0,
2148 node
=> get_standard_option
('pve-node'),
2149 vmid
=> get_standard_option
('pve-vmid'),
2150 skiplock
=> get_standard_option
('skiplock'),
2153 description
=> "The disk you want to resize.",
2154 enum
=> [PVE
::QemuServer
::disknames
()],
2158 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2159 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.",
2163 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2169 returns
=> { type
=> 'null'},
2173 my $rpcenv = PVE
::RPCEnvironment
::get
();
2175 my $authuser = $rpcenv->get_user();
2177 my $node = extract_param
($param, 'node');
2179 my $vmid = extract_param
($param, 'vmid');
2181 my $digest = extract_param
($param, 'digest');
2183 my $disk = extract_param
($param, 'disk');
2185 my $sizestr = extract_param
($param, 'size');
2187 my $skiplock = extract_param
($param, 'skiplock');
2188 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2189 if $skiplock && $authuser ne 'root@pam';
2191 my $storecfg = PVE
::Storage
::config
();
2193 my $updatefn = sub {
2195 my $conf = PVE
::QemuServer
::load_config
($vmid);
2197 die "checksum missmatch (file change by other user?)\n"
2198 if $digest && $digest ne $conf->{digest
};
2199 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2201 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2203 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2205 my $volid = $drive->{file
};
2207 die "disk '$disk' has no associated volume\n" if !$volid;
2209 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2211 die "you can't online resize a virtio windows bootdisk\n"
2212 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2214 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2216 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2218 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2220 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2221 my ($ext, $newsize, $unit) = ($1, $2, $4);
2224 $newsize = $newsize * 1024;
2225 } elsif ($unit eq 'M') {
2226 $newsize = $newsize * 1024 * 1024;
2227 } elsif ($unit eq 'G') {
2228 $newsize = $newsize * 1024 * 1024 * 1024;
2229 } elsif ($unit eq 'T') {
2230 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2233 $newsize += $size if $ext;
2234 $newsize = int($newsize);
2236 die "unable to skrink disk size\n" if $newsize < $size;
2238 return if $size == $newsize;
2240 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2242 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2244 $drive->{size
} = $newsize;
2245 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2247 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2250 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2254 __PACKAGE__-
>register_method({
2255 name
=> 'snapshot_list',
2256 path
=> '{vmid}/snapshot',
2258 description
=> "List all snapshots.",
2260 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2263 protected
=> 1, # qemu pid files are only readable by root
2265 additionalProperties
=> 0,
2267 vmid
=> get_standard_option
('pve-vmid'),
2268 node
=> get_standard_option
('pve-node'),
2277 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2282 my $vmid = $param->{vmid
};
2284 my $conf = PVE
::QemuServer
::load_config
($vmid);
2285 my $snaphash = $conf->{snapshots
} || {};
2289 foreach my $name (keys %$snaphash) {
2290 my $d = $snaphash->{$name};
2293 snaptime
=> $d->{snaptime
} || 0,
2294 vmstate
=> $d->{vmstate
} ?
1 : 0,
2295 description
=> $d->{description
} || '',
2297 $item->{parent
} = $d->{parent
} if $d->{parent
};
2298 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2302 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2303 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2304 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2306 push @$res, $current;
2311 __PACKAGE__-
>register_method({
2313 path
=> '{vmid}/snapshot',
2317 description
=> "Snapshot a VM.",
2319 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2322 additionalProperties
=> 0,
2324 node
=> get_standard_option
('pve-node'),
2325 vmid
=> get_standard_option
('pve-vmid'),
2326 snapname
=> get_standard_option
('pve-snapshot-name'),
2330 description
=> "Save the vmstate",
2335 description
=> "Freeze the filesystem",
2340 description
=> "A textual description or comment.",
2346 description
=> "the task ID.",
2351 my $rpcenv = PVE
::RPCEnvironment
::get
();
2353 my $authuser = $rpcenv->get_user();
2355 my $node = extract_param
($param, 'node');
2357 my $vmid = extract_param
($param, 'vmid');
2359 my $snapname = extract_param
($param, 'snapname');
2361 die "unable to use snapshot name 'current' (reserved name)\n"
2362 if $snapname eq 'current';
2365 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2366 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2367 $param->{freezefs
}, $param->{description
});
2370 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2373 __PACKAGE__-
>register_method({
2374 name
=> 'snapshot_cmd_idx',
2375 path
=> '{vmid}/snapshot/{snapname}',
2382 additionalProperties
=> 0,
2384 vmid
=> get_standard_option
('pve-vmid'),
2385 node
=> get_standard_option
('pve-node'),
2386 snapname
=> get_standard_option
('pve-snapshot-name'),
2395 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2402 push @$res, { cmd
=> 'rollback' };
2403 push @$res, { cmd
=> 'config' };
2408 __PACKAGE__-
>register_method({
2409 name
=> 'update_snapshot_config',
2410 path
=> '{vmid}/snapshot/{snapname}/config',
2414 description
=> "Update snapshot metadata.",
2416 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2419 additionalProperties
=> 0,
2421 node
=> get_standard_option
('pve-node'),
2422 vmid
=> get_standard_option
('pve-vmid'),
2423 snapname
=> get_standard_option
('pve-snapshot-name'),
2427 description
=> "A textual description or comment.",
2431 returns
=> { type
=> 'null' },
2435 my $rpcenv = PVE
::RPCEnvironment
::get
();
2437 my $authuser = $rpcenv->get_user();
2439 my $vmid = extract_param
($param, 'vmid');
2441 my $snapname = extract_param
($param, 'snapname');
2443 return undef if !defined($param->{description
});
2445 my $updatefn = sub {
2447 my $conf = PVE
::QemuServer
::load_config
($vmid);
2449 PVE
::QemuServer
::check_lock
($conf);
2451 my $snap = $conf->{snapshots
}->{$snapname};
2453 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2455 $snap->{description
} = $param->{description
} if defined($param->{description
});
2457 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2460 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2465 __PACKAGE__-
>register_method({
2466 name
=> 'get_snapshot_config',
2467 path
=> '{vmid}/snapshot/{snapname}/config',
2470 description
=> "Get snapshot configuration",
2472 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2475 additionalProperties
=> 0,
2477 node
=> get_standard_option
('pve-node'),
2478 vmid
=> get_standard_option
('pve-vmid'),
2479 snapname
=> get_standard_option
('pve-snapshot-name'),
2482 returns
=> { type
=> "object" },
2486 my $rpcenv = PVE
::RPCEnvironment
::get
();
2488 my $authuser = $rpcenv->get_user();
2490 my $vmid = extract_param
($param, 'vmid');
2492 my $snapname = extract_param
($param, 'snapname');
2494 my $conf = PVE
::QemuServer
::load_config
($vmid);
2496 my $snap = $conf->{snapshots
}->{$snapname};
2498 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2503 __PACKAGE__-
>register_method({
2505 path
=> '{vmid}/snapshot/{snapname}/rollback',
2509 description
=> "Rollback VM state to specified snapshot.",
2511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2514 additionalProperties
=> 0,
2516 node
=> get_standard_option
('pve-node'),
2517 vmid
=> get_standard_option
('pve-vmid'),
2518 snapname
=> get_standard_option
('pve-snapshot-name'),
2523 description
=> "the task ID.",
2528 my $rpcenv = PVE
::RPCEnvironment
::get
();
2530 my $authuser = $rpcenv->get_user();
2532 my $node = extract_param
($param, 'node');
2534 my $vmid = extract_param
($param, 'vmid');
2536 my $snapname = extract_param
($param, 'snapname');
2539 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2540 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2543 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2546 __PACKAGE__-
>register_method({
2547 name
=> 'delsnapshot',
2548 path
=> '{vmid}/snapshot/{snapname}',
2552 description
=> "Delete a VM snapshot.",
2554 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2557 additionalProperties
=> 0,
2559 node
=> get_standard_option
('pve-node'),
2560 vmid
=> get_standard_option
('pve-vmid'),
2561 snapname
=> get_standard_option
('pve-snapshot-name'),
2565 description
=> "For removal from config file, even if removing disk snapshots fails.",
2571 description
=> "the task ID.",
2576 my $rpcenv = PVE
::RPCEnvironment
::get
();
2578 my $authuser = $rpcenv->get_user();
2580 my $node = extract_param
($param, 'node');
2582 my $vmid = extract_param
($param, 'vmid');
2584 my $snapname = extract_param
($param, 'snapname');
2587 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2588 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2591 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2594 __PACKAGE__-
>register_method({
2596 path
=> '{vmid}/template',
2600 description
=> "Create a Template.",
2602 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
2604 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2605 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2609 additionalProperties
=> 0,
2611 node
=> get_standard_option
('pve-node'),
2612 vmid
=> get_standard_option
('pve-vmid'),
2616 description
=> "If you want to convert only 1 disk to base image.",
2617 enum
=> [PVE
::QemuServer
::disknames
()],
2622 returns
=> { type
=> 'null'},
2626 my $rpcenv = PVE
::RPCEnvironment
::get
();
2628 my $authuser = $rpcenv->get_user();
2630 my $node = extract_param
($param, 'node');
2632 my $vmid = extract_param
($param, 'vmid');
2634 my $disk = extract_param
($param, 'disk');
2636 my $updatefn = sub {
2638 my $conf = PVE
::QemuServer
::load_config
($vmid);
2640 PVE
::QemuServer
::check_lock
($conf);
2642 die "unable to create template, because VM contains snapshots\n"
2643 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2645 die "you can't convert a template to a template\n"
2646 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2648 die "you can't convert a VM to template if VM is running\n"
2649 if PVE
::QemuServer
::check_running
($vmid);
2652 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2655 $conf->{template
} = 1;
2656 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2658 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2661 PVE
::QemuServer
::lock_config
($vmid, $updatefn);