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 raise_perm_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_clone = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
67 PVE
::QemuServer
::foreach_drive
($conf, sub {
68 my ($ds, $drive) = @_;
70 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
72 my $volid = $drive->{file
};
74 return if !$volid || $volid eq 'none';
77 if ($volid eq 'cdrom') {
78 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
83 $sharedvm = 0 if !$scfg->{shared
};
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
91 $sid = $storage if $storage;
92 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
99 # Note: $pool is only needed when creating a VM, because pool permissions
100 # are automatically inherited if VM already exists inside a pool.
101 my $create_disks = sub {
102 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
107 PVE
::QemuServer
::foreach_drive
($settings, sub {
108 my ($ds, $disk) = @_;
110 my $volid = $disk->{file
};
112 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
113 delete $disk->{size
};
114 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
115 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
116 my ($storeid, $size) = ($2 || $default_storage, $3);
117 die "no storage ID specified (and no default storage)\n" if !$storeid;
118 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
119 my $fmt = $disk->{format
} || $defformat;
120 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
121 $fmt, undef, $size*1024*1024);
122 $disk->{file
} = $volid;
123 $disk->{size
} = $size*1024*1024*1024;
124 push @$vollist, $volid;
125 delete $disk->{format
}; # no longer needed
126 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
129 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
131 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
133 my $foundvolid = undef;
136 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
137 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
139 PVE
::Storage
::foreach_volid
($dl, sub {
141 if($volumeid eq $volid) {
148 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
150 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
151 $disk->{size
} = $size;
152 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
156 # free allocated images on error
158 syslog
('err', "VM $vmid creating disks failed");
159 foreach my $volid (@$vollist) {
160 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
166 # modify vm config if everything went well
167 foreach my $ds (keys %$res) {
168 $conf->{$ds} = $res->{$ds};
174 my $check_vm_modify_config_perm = sub {
175 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
177 return 1 if $authuser eq 'root@pam';
179 foreach my $opt (@$key_list) {
180 # disk checks need to be done somewhere else
181 next if PVE
::QemuServer
::valid_drivename
($opt);
183 if ($opt eq 'sockets' || $opt eq 'cores' ||
184 $opt eq 'cpu' || $opt eq 'smp' ||
185 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
186 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
187 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
189 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
191 } elsif ($opt eq 'args' || $opt eq 'lock') {
192 die "only root can set '$opt' config\n";
193 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
194 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
195 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
196 } elsif ($opt =~ m/^net\d+$/) {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
206 __PACKAGE__-
>register_method({
210 description
=> "Virtual machine index (per node).",
212 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
216 protected
=> 1, # qemu pid files are only readable by root
218 additionalProperties
=> 0,
220 node
=> get_standard_option
('pve-node'),
229 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
234 my $rpcenv = PVE
::RPCEnvironment
::get
();
235 my $authuser = $rpcenv->get_user();
237 my $vmstatus = PVE
::QemuServer
::vmstatus
();
240 foreach my $vmid (keys %$vmstatus) {
241 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
243 my $data = $vmstatus->{$vmid};
244 $data->{vmid
} = $vmid;
253 __PACKAGE__-
>register_method({
257 description
=> "Create or restore a virtual machine.",
259 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
260 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
261 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
262 user
=> 'all', # check inside
267 additionalProperties
=> 0,
268 properties
=> PVE
::QemuServer
::json_config_properties
(
270 node
=> get_standard_option
('pve-node'),
271 vmid
=> get_standard_option
('pve-vmid'),
273 description
=> "The backup file.",
278 storage
=> get_standard_option
('pve-storage-id', {
279 description
=> "Default storage.",
285 description
=> "Allow to overwrite existing VM.",
286 requires
=> 'archive',
291 description
=> "Assign a unique random ethernet address.",
292 requires
=> 'archive',
296 type
=> 'string', format
=> 'pve-poolid',
297 description
=> "Add the VM to the specified pool.",
307 my $rpcenv = PVE
::RPCEnvironment
::get
();
309 my $authuser = $rpcenv->get_user();
311 my $node = extract_param
($param, 'node');
313 my $vmid = extract_param
($param, 'vmid');
315 my $archive = extract_param
($param, 'archive');
317 my $storage = extract_param
($param, 'storage');
319 my $force = extract_param
($param, 'force');
321 my $unique = extract_param
($param, 'unique');
323 my $pool = extract_param
($param, 'pool');
325 my $filename = PVE
::QemuServer
::config_file
($vmid);
327 my $storecfg = PVE
::Storage
::config
();
329 PVE
::Cluster
::check_cfs_quorum
();
331 if (defined($pool)) {
332 $rpcenv->check_pool_exist($pool);
335 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
336 if defined($storage);
338 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
340 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
342 } elsif ($archive && $force && (-f
$filename) &&
343 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
344 # OK: user has VM.Backup permissions, and want to restore an existing VM
350 &$resolve_cdrom_alias($param);
352 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
354 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
356 foreach my $opt (keys %$param) {
357 if (PVE
::QemuServer
::valid_drivename
($opt)) {
358 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
359 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
361 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
362 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
366 PVE
::QemuServer
::add_random_macs
($param);
368 my $keystr = join(' ', keys %$param);
369 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
371 if ($archive eq '-') {
372 die "pipe requires cli environment\n"
373 if $rpcenv->{type
} ne 'cli';
375 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
377 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
378 if PVE
::Storage
::parse_volume_id
($archive, 1);
380 die "can't find archive file '$archive'\n" if !($path && -f
$path);
385 my $restorefn = sub {
387 # fixme: this test does not work if VM exists on other node!
389 die "unable to restore vm $vmid: config file already exists\n"
392 die "unable to restore vm $vmid: vm is running\n"
393 if PVE
::QemuServer
::check_running
($vmid);
397 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
400 unique
=> $unique });
402 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
405 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
411 die "unable to create vm $vmid: config file already exists\n"
422 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
424 # try to be smart about bootdisk
425 my @disks = PVE
::QemuServer
::disknames
();
427 foreach my $ds (reverse @disks) {
428 next if !$conf->{$ds};
429 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
430 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
434 if (!$conf->{bootdisk
} && $firstdisk) {
435 $conf->{bootdisk
} = $firstdisk;
438 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
444 foreach my $volid (@$vollist) {
445 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
448 die "create failed - $err";
451 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
454 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
457 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
460 __PACKAGE__-
>register_method({
465 description
=> "Directory index",
470 additionalProperties
=> 0,
472 node
=> get_standard_option
('pve-node'),
473 vmid
=> get_standard_option
('pve-vmid'),
481 subdir
=> { type
=> 'string' },
484 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
490 { subdir
=> 'config' },
491 { subdir
=> 'status' },
492 { subdir
=> 'unlink' },
493 { subdir
=> 'vncproxy' },
494 { subdir
=> 'migrate' },
495 { subdir
=> 'resize' },
497 { subdir
=> 'rrddata' },
498 { subdir
=> 'monitor' },
499 { subdir
=> 'snapshot' },
505 __PACKAGE__-
>register_method({
507 path
=> '{vmid}/rrd',
509 protected
=> 1, # fixme: can we avoid that?
511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
513 description
=> "Read VM RRD statistics (returns PNG)",
515 additionalProperties
=> 0,
517 node
=> get_standard_option
('pve-node'),
518 vmid
=> get_standard_option
('pve-vmid'),
520 description
=> "Specify the time frame you are interested in.",
522 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
525 description
=> "The list of datasources you want to display.",
526 type
=> 'string', format
=> 'pve-configid-list',
529 description
=> "The RRD consolidation function",
531 enum
=> [ 'AVERAGE', 'MAX' ],
539 filename
=> { type
=> 'string' },
545 return PVE
::Cluster
::create_rrd_graph
(
546 "pve2-vm/$param->{vmid}", $param->{timeframe
},
547 $param->{ds
}, $param->{cf
});
551 __PACKAGE__-
>register_method({
553 path
=> '{vmid}/rrddata',
555 protected
=> 1, # fixme: can we avoid that?
557 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
559 description
=> "Read VM RRD statistics",
561 additionalProperties
=> 0,
563 node
=> get_standard_option
('pve-node'),
564 vmid
=> get_standard_option
('pve-vmid'),
566 description
=> "Specify the time frame you are interested in.",
568 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
571 description
=> "The RRD consolidation function",
573 enum
=> [ 'AVERAGE', 'MAX' ],
588 return PVE
::Cluster
::create_rrd_data
(
589 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
593 __PACKAGE__-
>register_method({
595 path
=> '{vmid}/config',
598 description
=> "Get virtual machine configuration.",
600 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
603 additionalProperties
=> 0,
605 node
=> get_standard_option
('pve-node'),
606 vmid
=> get_standard_option
('pve-vmid'),
614 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
621 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
623 delete $conf->{snapshots
};
628 my $vm_is_volid_owner = sub {
629 my ($storecfg, $vmid, $volid) =@_;
631 if ($volid !~ m
|^/|) {
633 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
634 if ($owner && ($owner == $vmid)) {
642 my $test_deallocate_drive = sub {
643 my ($storecfg, $vmid, $key, $drive, $force) = @_;
645 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
646 my $volid = $drive->{file
};
647 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
648 if ($force || $key =~ m/^unused/) {
649 my $sid = PVE
::Storage
::parse_volume_id
($volid);
658 my $delete_drive = sub {
659 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
661 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
662 my $volid = $drive->{file
};
664 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
665 if ($force || $key =~ m/^unused/) {
667 # check if the disk is really unused
668 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
669 my $path = PVE
::Storage
::path
($storecfg, $volid);
671 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
672 if $used_paths->{$path};
674 PVE
::Storage
::vdisk_free
($storecfg, $volid);
678 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
683 delete $conf->{$key};
686 my $vmconfig_delete_option = sub {
687 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
689 return if !defined($conf->{$opt});
691 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
694 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
696 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
697 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
698 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
702 my $unplugwarning = "";
703 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
704 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
705 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
706 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
707 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
708 $unplugwarning = "<br>verify that your guest support acpi hotplug";
711 if($opt eq 'tablet'){
712 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
714 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
718 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
719 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
721 delete $conf->{$opt};
724 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
727 my $safe_num_ne = sub {
730 return 0 if !defined($a) && !defined($b);
731 return 1 if !defined($a);
732 return 1 if !defined($b);
737 my $vmconfig_update_disk = sub {
738 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
740 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
742 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
743 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
745 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
750 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
752 my $media = $drive->{media
} || 'disk';
753 my $oldmedia = $old_drive->{media
} || 'disk';
754 die "unable to change media type\n" if $media ne $oldmedia;
756 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
757 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
759 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
760 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
763 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
764 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
765 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
766 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
767 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
768 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
769 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
770 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
771 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
772 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
777 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
778 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
780 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
781 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
783 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
785 if (PVE
::QemuServer
::check_running
($vmid)) {
786 if ($drive->{file
} eq 'none') {
787 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
789 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
790 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
791 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
795 } else { # hotplug new disks
797 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
801 my $vmconfig_update_net = sub {
802 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
804 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
805 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
806 my $newnet = PVE
::QemuServer
::parse_net
($value);
808 if($oldnet->{model
} ne $newnet->{model
}){
809 #if model change, we try to hot-unplug
810 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
813 if($newnet->{bridge
} && $oldnet->{bridge
}){
814 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
816 if($newnet->{rate
} ne $oldnet->{rate
}){
817 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
820 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
821 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
822 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
826 #if bridge/nat mode change, we try to hot-unplug
827 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
832 $conf->{$opt} = $value;
833 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
834 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
836 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
838 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
841 my $vm_config_perm_list = [
851 __PACKAGE__-
>register_method({
853 path
=> '{vmid}/config',
857 description
=> "Set virtual machine options.",
859 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
862 additionalProperties
=> 0,
863 properties
=> PVE
::QemuServer
::json_config_properties
(
865 node
=> get_standard_option
('pve-node'),
866 vmid
=> get_standard_option
('pve-vmid'),
867 skiplock
=> get_standard_option
('skiplock'),
869 type
=> 'string', format
=> 'pve-configid-list',
870 description
=> "A list of settings you want to delete.",
875 description
=> $opt_force_description,
877 requires
=> 'delete',
881 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
887 returns
=> { type
=> 'null'},
891 my $rpcenv = PVE
::RPCEnvironment
::get
();
893 my $authuser = $rpcenv->get_user();
895 my $node = extract_param
($param, 'node');
897 my $vmid = extract_param
($param, 'vmid');
899 my $digest = extract_param
($param, 'digest');
901 my @paramarr = (); # used for log message
902 foreach my $key (keys %$param) {
903 push @paramarr, "-$key", $param->{$key};
906 my $skiplock = extract_param
($param, 'skiplock');
907 raise_param_exc
({ skiplock
=> "Only root may use this option." })
908 if $skiplock && $authuser ne 'root@pam';
910 my $delete_str = extract_param
($param, 'delete');
912 my $force = extract_param
($param, 'force');
914 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
916 my $storecfg = PVE
::Storage
::config
();
918 my $defaults = PVE
::QemuServer
::load_defaults
();
920 &$resolve_cdrom_alias($param);
922 # now try to verify all parameters
925 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
926 $opt = 'ide2' if $opt eq 'cdrom';
927 raise_param_exc
({ delete => "you can't use '-$opt' and " .
928 "-delete $opt' at the same time" })
929 if defined($param->{$opt});
931 if (!PVE
::QemuServer
::option_exists
($opt)) {
932 raise_param_exc
({ delete => "unknown option '$opt'" });
938 foreach my $opt (keys %$param) {
939 if (PVE
::QemuServer
::valid_drivename
($opt)) {
941 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
942 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
943 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
944 } elsif ($opt =~ m/^net(\d+)$/) {
946 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
947 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
951 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
953 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
955 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
959 my $conf = PVE
::QemuServer
::load_config
($vmid);
961 die "checksum missmatch (file change by other user?)\n"
962 if $digest && $digest ne $conf->{digest
};
964 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
966 if ($param->{memory
} || defined($param->{balloon
})) {
967 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
968 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
970 die "balloon value too large (must be smaller than assigned memory)\n"
971 if $balloon && $balloon > $maxmem;
974 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
976 foreach my $opt (@delete) { # delete
977 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
978 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
981 my $running = PVE
::QemuServer
::check_running
($vmid);
983 foreach my $opt (keys %$param) { # add/change
985 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
987 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
989 if (PVE
::QemuServer
::valid_drivename
($opt)) {
991 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
992 $opt, $param->{$opt}, $force);
994 } elsif ($opt =~ m/^net(\d+)$/) { #nics
996 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
997 $opt, $param->{$opt});
1001 if($opt eq 'tablet' && $param->{$opt} == 1){
1002 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1003 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
1004 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1007 $conf->{$opt} = $param->{$opt};
1008 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1012 # allow manual ballooning if shares is set to zero
1013 if ($running && defined($param->{balloon
}) &&
1014 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1015 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1016 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1021 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1027 __PACKAGE__-
>register_method({
1028 name
=> 'destroy_vm',
1033 description
=> "Destroy the vm (also delete all used/owned volumes).",
1035 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1038 additionalProperties
=> 0,
1040 node
=> get_standard_option
('pve-node'),
1041 vmid
=> get_standard_option
('pve-vmid'),
1042 skiplock
=> get_standard_option
('skiplock'),
1051 my $rpcenv = PVE
::RPCEnvironment
::get
();
1053 my $authuser = $rpcenv->get_user();
1055 my $vmid = $param->{vmid
};
1057 my $skiplock = $param->{skiplock
};
1058 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1059 if $skiplock && $authuser ne 'root@pam';
1062 my $conf = PVE
::QemuServer
::load_config
($vmid);
1064 my $storecfg = PVE
::Storage
::config
();
1066 my $delVMfromPoolFn = sub {
1067 my $usercfg = cfs_read_file
("user.cfg");
1068 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1069 if (my $data = $usercfg->{pools
}->{$pool}) {
1070 delete $data->{vms
}->{$vmid};
1071 delete $usercfg->{vms
}->{$vmid};
1072 cfs_write_file
("user.cfg", $usercfg);
1080 syslog
('info', "destroy VM $vmid: $upid\n");
1082 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1084 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1087 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1090 __PACKAGE__-
>register_method({
1092 path
=> '{vmid}/unlink',
1096 description
=> "Unlink/delete disk images.",
1098 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1101 additionalProperties
=> 0,
1103 node
=> get_standard_option
('pve-node'),
1104 vmid
=> get_standard_option
('pve-vmid'),
1106 type
=> 'string', format
=> 'pve-configid-list',
1107 description
=> "A list of disk IDs you want to delete.",
1111 description
=> $opt_force_description,
1116 returns
=> { type
=> 'null'},
1120 $param->{delete} = extract_param
($param, 'idlist');
1122 __PACKAGE__-
>update_vm($param);
1129 __PACKAGE__-
>register_method({
1131 path
=> '{vmid}/vncproxy',
1135 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1137 description
=> "Creates a TCP VNC proxy connections.",
1139 additionalProperties
=> 0,
1141 node
=> get_standard_option
('pve-node'),
1142 vmid
=> get_standard_option
('pve-vmid'),
1146 additionalProperties
=> 0,
1148 user
=> { type
=> 'string' },
1149 ticket
=> { type
=> 'string' },
1150 cert
=> { type
=> 'string' },
1151 port
=> { type
=> 'integer' },
1152 upid
=> { type
=> 'string' },
1158 my $rpcenv = PVE
::RPCEnvironment
::get
();
1160 my $authuser = $rpcenv->get_user();
1162 my $vmid = $param->{vmid
};
1163 my $node = $param->{node
};
1165 my $authpath = "/vms/$vmid";
1167 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1169 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1172 my $port = PVE
::Tools
::next_vnc_port
();
1176 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1177 $remip = PVE
::Cluster
::remote_node_ip
($node);
1180 # NOTE: kvm VNC traffic is already TLS encrypted
1181 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1188 syslog
('info', "starting vnc proxy $upid\n");
1190 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1192 my $qmstr = join(' ', @$qmcmd);
1194 # also redirect stderr (else we get RFB protocol errors)
1195 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1197 PVE
::Tools
::run_command
($cmd);
1202 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1204 PVE
::Tools
::wait_for_vnc_port
($port);
1215 __PACKAGE__-
>register_method({
1217 path
=> '{vmid}/status',
1220 description
=> "Directory index",
1225 additionalProperties
=> 0,
1227 node
=> get_standard_option
('pve-node'),
1228 vmid
=> get_standard_option
('pve-vmid'),
1236 subdir
=> { type
=> 'string' },
1239 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1245 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1248 { subdir
=> 'current' },
1249 { subdir
=> 'start' },
1250 { subdir
=> 'stop' },
1256 my $vm_is_ha_managed = sub {
1259 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1260 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1266 __PACKAGE__-
>register_method({
1267 name
=> 'vm_status',
1268 path
=> '{vmid}/status/current',
1271 protected
=> 1, # qemu pid files are only readable by root
1272 description
=> "Get virtual machine status.",
1274 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1277 additionalProperties
=> 0,
1279 node
=> get_standard_option
('pve-node'),
1280 vmid
=> get_standard_option
('pve-vmid'),
1283 returns
=> { type
=> 'object' },
1288 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1290 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1291 my $status = $vmstatus->{$param->{vmid
}};
1293 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1298 __PACKAGE__-
>register_method({
1300 path
=> '{vmid}/status/start',
1304 description
=> "Start virtual machine.",
1306 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1309 additionalProperties
=> 0,
1311 node
=> get_standard_option
('pve-node'),
1312 vmid
=> get_standard_option
('pve-vmid'),
1313 skiplock
=> get_standard_option
('skiplock'),
1314 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1315 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1325 my $rpcenv = PVE
::RPCEnvironment
::get
();
1327 my $authuser = $rpcenv->get_user();
1329 my $node = extract_param
($param, 'node');
1331 my $vmid = extract_param
($param, 'vmid');
1333 my $stateuri = extract_param
($param, 'stateuri');
1334 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1335 if $stateuri && $authuser ne 'root@pam';
1337 my $skiplock = extract_param
($param, 'skiplock');
1338 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1339 if $skiplock && $authuser ne 'root@pam';
1341 my $migratedfrom = extract_param
($param, 'migratedfrom');
1342 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1343 if $migratedfrom && $authuser ne 'root@pam';
1345 my $storecfg = PVE
::Storage
::config
();
1347 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1348 $rpcenv->{type
} ne 'ha') {
1353 my $service = "pvevm:$vmid";
1355 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1357 print "Executing HA start for VM $vmid\n";
1359 PVE
::Tools
::run_command
($cmd);
1364 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1371 syslog
('info', "start VM $vmid: $upid\n");
1373 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1378 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1382 __PACKAGE__-
>register_method({
1384 path
=> '{vmid}/status/stop',
1388 description
=> "Stop virtual machine.",
1390 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1393 additionalProperties
=> 0,
1395 node
=> get_standard_option
('pve-node'),
1396 vmid
=> get_standard_option
('pve-vmid'),
1397 skiplock
=> get_standard_option
('skiplock'),
1398 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1400 description
=> "Wait maximal timeout seconds.",
1406 description
=> "Do not decativate storage volumes.",
1419 my $rpcenv = PVE
::RPCEnvironment
::get
();
1421 my $authuser = $rpcenv->get_user();
1423 my $node = extract_param
($param, 'node');
1425 my $vmid = extract_param
($param, 'vmid');
1427 my $skiplock = extract_param
($param, 'skiplock');
1428 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1429 if $skiplock && $authuser ne 'root@pam';
1431 my $keepActive = extract_param
($param, 'keepActive');
1432 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1433 if $keepActive && $authuser ne 'root@pam';
1435 my $migratedfrom = extract_param
($param, 'migratedfrom');
1436 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1437 if $migratedfrom && $authuser ne 'root@pam';
1440 my $storecfg = PVE
::Storage
::config
();
1442 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1447 my $service = "pvevm:$vmid";
1449 my $cmd = ['clusvcadm', '-d', $service];
1451 print "Executing HA stop for VM $vmid\n";
1453 PVE
::Tools
::run_command
($cmd);
1458 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1464 syslog
('info', "stop VM $vmid: $upid\n");
1466 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1467 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1472 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1476 __PACKAGE__-
>register_method({
1478 path
=> '{vmid}/status/reset',
1482 description
=> "Reset virtual machine.",
1484 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1487 additionalProperties
=> 0,
1489 node
=> get_standard_option
('pve-node'),
1490 vmid
=> get_standard_option
('pve-vmid'),
1491 skiplock
=> get_standard_option
('skiplock'),
1500 my $rpcenv = PVE
::RPCEnvironment
::get
();
1502 my $authuser = $rpcenv->get_user();
1504 my $node = extract_param
($param, 'node');
1506 my $vmid = extract_param
($param, 'vmid');
1508 my $skiplock = extract_param
($param, 'skiplock');
1509 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1510 if $skiplock && $authuser ne 'root@pam';
1512 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1517 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1522 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1525 __PACKAGE__-
>register_method({
1526 name
=> 'vm_shutdown',
1527 path
=> '{vmid}/status/shutdown',
1531 description
=> "Shutdown virtual machine.",
1533 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1536 additionalProperties
=> 0,
1538 node
=> get_standard_option
('pve-node'),
1539 vmid
=> get_standard_option
('pve-vmid'),
1540 skiplock
=> get_standard_option
('skiplock'),
1542 description
=> "Wait maximal timeout seconds.",
1548 description
=> "Make sure the VM stops.",
1554 description
=> "Do not decativate storage volumes.",
1567 my $rpcenv = PVE
::RPCEnvironment
::get
();
1569 my $authuser = $rpcenv->get_user();
1571 my $node = extract_param
($param, 'node');
1573 my $vmid = extract_param
($param, 'vmid');
1575 my $skiplock = extract_param
($param, 'skiplock');
1576 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1577 if $skiplock && $authuser ne 'root@pam';
1579 my $keepActive = extract_param
($param, 'keepActive');
1580 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1581 if $keepActive && $authuser ne 'root@pam';
1583 my $storecfg = PVE
::Storage
::config
();
1588 syslog
('info', "shutdown VM $vmid: $upid\n");
1590 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1591 1, $param->{forceStop
}, $keepActive);
1596 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1599 __PACKAGE__-
>register_method({
1600 name
=> 'vm_suspend',
1601 path
=> '{vmid}/status/suspend',
1605 description
=> "Suspend virtual machine.",
1607 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1610 additionalProperties
=> 0,
1612 node
=> get_standard_option
('pve-node'),
1613 vmid
=> get_standard_option
('pve-vmid'),
1614 skiplock
=> get_standard_option
('skiplock'),
1623 my $rpcenv = PVE
::RPCEnvironment
::get
();
1625 my $authuser = $rpcenv->get_user();
1627 my $node = extract_param
($param, 'node');
1629 my $vmid = extract_param
($param, 'vmid');
1631 my $skiplock = extract_param
($param, 'skiplock');
1632 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1633 if $skiplock && $authuser ne 'root@pam';
1635 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1640 syslog
('info', "suspend VM $vmid: $upid\n");
1642 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1647 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1650 __PACKAGE__-
>register_method({
1651 name
=> 'vm_resume',
1652 path
=> '{vmid}/status/resume',
1656 description
=> "Resume virtual machine.",
1658 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1661 additionalProperties
=> 0,
1663 node
=> get_standard_option
('pve-node'),
1664 vmid
=> get_standard_option
('pve-vmid'),
1665 skiplock
=> get_standard_option
('skiplock'),
1674 my $rpcenv = PVE
::RPCEnvironment
::get
();
1676 my $authuser = $rpcenv->get_user();
1678 my $node = extract_param
($param, 'node');
1680 my $vmid = extract_param
($param, 'vmid');
1682 my $skiplock = extract_param
($param, 'skiplock');
1683 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1684 if $skiplock && $authuser ne 'root@pam';
1686 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1691 syslog
('info', "resume VM $vmid: $upid\n");
1693 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1698 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1701 __PACKAGE__-
>register_method({
1702 name
=> 'vm_sendkey',
1703 path
=> '{vmid}/sendkey',
1707 description
=> "Send key event to virtual machine.",
1709 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1712 additionalProperties
=> 0,
1714 node
=> get_standard_option
('pve-node'),
1715 vmid
=> get_standard_option
('pve-vmid'),
1716 skiplock
=> get_standard_option
('skiplock'),
1718 description
=> "The key (qemu monitor encoding).",
1723 returns
=> { type
=> 'null'},
1727 my $rpcenv = PVE
::RPCEnvironment
::get
();
1729 my $authuser = $rpcenv->get_user();
1731 my $node = extract_param
($param, 'node');
1733 my $vmid = extract_param
($param, 'vmid');
1735 my $skiplock = extract_param
($param, 'skiplock');
1736 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1737 if $skiplock && $authuser ne 'root@pam';
1739 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1744 __PACKAGE__-
>register_method({
1745 name
=> 'vm_feature',
1746 path
=> '{vmid}/feature',
1750 description
=> "Check if feature for virtual machine is available.",
1752 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1755 additionalProperties
=> 0,
1757 node
=> get_standard_option
('pve-node'),
1758 vmid
=> get_standard_option
('pve-vmid'),
1760 description
=> "Feature to check.",
1762 enum
=> [ 'snapshot', 'clone', 'copy' ],
1764 snapname
=> get_standard_option
('pve-snapshot-name', {
1772 hasFeature
=> { type
=> 'boolean' },
1775 items
=> { type
=> 'string' },
1782 my $node = extract_param
($param, 'node');
1784 my $vmid = extract_param
($param, 'vmid');
1786 my $snapname = extract_param
($param, 'snapname');
1788 my $feature = extract_param
($param, 'feature');
1790 my $running = PVE
::QemuServer
::check_running
($vmid);
1792 my $conf = PVE
::QemuServer
::load_config
($vmid);
1795 my $snap = $conf->{snapshots
}->{$snapname};
1796 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1799 my $storecfg = PVE
::Storage
::config
();
1801 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1802 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1805 hasFeature
=> $hasFeature,
1806 nodes
=> [ keys %$nodelist ],
1810 __PACKAGE__-
>register_method({
1812 path
=> '{vmid}/clone',
1816 description
=> "Create a copy of virtual machine/template.",
1818 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1819 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1820 "'Datastore.AllocateSpace' on any used storage.",
1823 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1825 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1826 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1831 additionalProperties
=> 0,
1833 node
=> get_standard_option
('pve-node'),
1834 vmid
=> get_standard_option
('pve-vmid'),
1835 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1838 type
=> 'string', format
=> 'dns-name',
1839 description
=> "Set a name for the new VM.",
1844 description
=> "Description for the new VM.",
1848 type
=> 'string', format
=> 'pve-poolid',
1849 description
=> "Add the new VM to the specified pool.",
1851 snapname
=> get_standard_option
('pve-snapshot-name', {
1855 storage
=> get_standard_option
('pve-storage-id', {
1856 description
=> "Target storage for full clone.",
1861 description
=> "Target format for file storage.",
1865 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1870 description
=> "Create a full copy of all disk. This is always done when " .
1871 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1874 target
=> get_standard_option
('pve-node', {
1875 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1886 my $rpcenv = PVE
::RPCEnvironment
::get
();
1888 my $authuser = $rpcenv->get_user();
1890 my $node = extract_param
($param, 'node');
1892 my $vmid = extract_param
($param, 'vmid');
1894 my $newid = extract_param
($param, 'newid');
1896 my $pool = extract_param
($param, 'pool');
1898 if (defined($pool)) {
1899 $rpcenv->check_pool_exist($pool);
1902 my $snapname = extract_param
($param, 'snapname');
1904 my $storage = extract_param
($param, 'storage');
1906 my $format = extract_param
($param, 'format');
1908 my $target = extract_param
($param, 'target');
1910 my $localnode = PVE
::INotify
::nodename
();
1912 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1914 PVE
::Cluster
::check_node_exists
($target) if $target;
1916 my $storecfg = PVE
::Storage
::config
();
1919 # check if storage is enabled on local node
1920 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1922 # check if storage is available on target node
1923 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1924 # clone only works if target storage is shared
1925 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1926 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1930 PVE
::Cluster
::check_cfs_quorum
();
1932 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1934 # exclusive lock if VM is running - else shared lock is enough;
1935 my $shared_lock = $running ?
0 : 1;
1939 # do all tests after lock
1940 # we also try to do all tests before we fork the worker
1942 my $conf = PVE
::QemuServer
::load_config
($vmid);
1944 PVE
::QemuServer
::check_lock
($conf);
1946 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1948 die "unexpected state change\n" if $verify_running != $running;
1950 die "snapshot '$snapname' does not exist\n"
1951 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1953 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1955 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1957 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1959 my $conffile = PVE
::QemuServer
::config_file
($newid);
1961 die "unable to create VM $newid: config file already exists\n"
1964 my $newconf = { lock => 'clone' };
1968 foreach my $opt (keys %$oldconf) {
1969 my $value = $oldconf->{$opt};
1971 # do not copy snapshot related info
1972 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1973 $opt eq 'vmstate' || $opt eq 'snapstate';
1975 # always change MAC! address
1976 if ($opt =~ m/^net(\d+)$/) {
1977 my $net = PVE
::QemuServer
::parse_net
($value);
1978 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1979 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1980 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1981 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1982 $newconf->{$opt} = $value; # simply copy configuration
1984 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1985 die "Full clone feature is not available"
1986 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
1989 $drives->{$opt} = $drive;
1990 push @$vollist, $drive->{file
};
1993 # copy everything else
1994 $newconf->{$opt} = $value;
1998 delete $newconf->{template
};
2000 if ($param->{name
}) {
2001 $newconf->{name
} = $param->{name
};
2003 if ($oldconf->{name
}) {
2004 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2006 $newconf->{name
} = "Copy-of-VM-$vmid";
2010 if ($param->{description
}) {
2011 $newconf->{description
} = $param->{description
};
2014 # create empty/temp config - this fails if VM already exists on other node
2015 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2020 my $newvollist = [];
2023 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2025 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2027 foreach my $opt (keys %$drives) {
2028 my $drive = $drives->{$opt};
2030 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2031 $newid, $storage, $format, $drive->{full
}, $newvollist);
2033 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2035 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2038 delete $newconf->{lock};
2039 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2042 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2043 die "Failed to move config to node '$target' - rename failed: $!\n"
2044 if !rename($conffile, $newconffile);
2047 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2052 sleep 1; # some storage like rbd need to wait before release volume - really?
2054 foreach my $volid (@$newvollist) {
2055 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2058 die "clone failed: $err";
2064 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2067 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2068 # Aquire exclusive lock lock for $newid
2069 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2074 __PACKAGE__-
>register_method({
2075 name
=> 'migrate_vm',
2076 path
=> '{vmid}/migrate',
2080 description
=> "Migrate virtual machine. Creates a new migration task.",
2082 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2085 additionalProperties
=> 0,
2087 node
=> get_standard_option
('pve-node'),
2088 vmid
=> get_standard_option
('pve-vmid'),
2089 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2092 description
=> "Use online/live migration.",
2097 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2104 description
=> "the task ID.",
2109 my $rpcenv = PVE
::RPCEnvironment
::get
();
2111 my $authuser = $rpcenv->get_user();
2113 my $target = extract_param
($param, 'target');
2115 my $localnode = PVE
::INotify
::nodename
();
2116 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2118 PVE
::Cluster
::check_cfs_quorum
();
2120 PVE
::Cluster
::check_node_exists
($target);
2122 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2124 my $vmid = extract_param
($param, 'vmid');
2126 raise_param_exc
({ force
=> "Only root may use this option." })
2127 if $param->{force
} && $authuser ne 'root@pam';
2130 my $conf = PVE
::QemuServer
::load_config
($vmid);
2132 # try to detect errors early
2134 PVE
::QemuServer
::check_lock
($conf);
2136 if (PVE
::QemuServer
::check_running
($vmid)) {
2137 die "cant migrate running VM without --online\n"
2138 if !$param->{online
};
2141 my $storecfg = PVE
::Storage
::config
();
2142 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2144 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2149 my $service = "pvevm:$vmid";
2151 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2153 print "Executing HA migrate for VM $vmid to node $target\n";
2155 PVE
::Tools
::run_command
($cmd);
2160 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2167 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2170 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2175 __PACKAGE__-
>register_method({
2177 path
=> '{vmid}/monitor',
2181 description
=> "Execute Qemu monitor commands.",
2183 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2186 additionalProperties
=> 0,
2188 node
=> get_standard_option
('pve-node'),
2189 vmid
=> get_standard_option
('pve-vmid'),
2192 description
=> "The monitor command.",
2196 returns
=> { type
=> 'string'},
2200 my $vmid = $param->{vmid
};
2202 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2206 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2208 $res = "ERROR: $@" if $@;
2213 __PACKAGE__-
>register_method({
2214 name
=> 'resize_vm',
2215 path
=> '{vmid}/resize',
2219 description
=> "Extend volume size.",
2221 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2224 additionalProperties
=> 0,
2226 node
=> get_standard_option
('pve-node'),
2227 vmid
=> get_standard_option
('pve-vmid'),
2228 skiplock
=> get_standard_option
('skiplock'),
2231 description
=> "The disk you want to resize.",
2232 enum
=> [PVE
::QemuServer
::disknames
()],
2236 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2237 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.",
2241 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2247 returns
=> { type
=> 'null'},
2251 my $rpcenv = PVE
::RPCEnvironment
::get
();
2253 my $authuser = $rpcenv->get_user();
2255 my $node = extract_param
($param, 'node');
2257 my $vmid = extract_param
($param, 'vmid');
2259 my $digest = extract_param
($param, 'digest');
2261 my $disk = extract_param
($param, 'disk');
2263 my $sizestr = extract_param
($param, 'size');
2265 my $skiplock = extract_param
($param, 'skiplock');
2266 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2267 if $skiplock && $authuser ne 'root@pam';
2269 my $storecfg = PVE
::Storage
::config
();
2271 my $updatefn = sub {
2273 my $conf = PVE
::QemuServer
::load_config
($vmid);
2275 die "checksum missmatch (file change by other user?)\n"
2276 if $digest && $digest ne $conf->{digest
};
2277 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2279 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2281 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2283 my $volid = $drive->{file
};
2285 die "disk '$disk' has no associated volume\n" if !$volid;
2287 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2289 die "you can't online resize a virtio windows bootdisk\n"
2290 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2292 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2294 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2296 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2298 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2299 my ($ext, $newsize, $unit) = ($1, $2, $4);
2302 $newsize = $newsize * 1024;
2303 } elsif ($unit eq 'M') {
2304 $newsize = $newsize * 1024 * 1024;
2305 } elsif ($unit eq 'G') {
2306 $newsize = $newsize * 1024 * 1024 * 1024;
2307 } elsif ($unit eq 'T') {
2308 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2311 $newsize += $size if $ext;
2312 $newsize = int($newsize);
2314 die "unable to skrink disk size\n" if $newsize < $size;
2316 return if $size == $newsize;
2318 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2320 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2322 $drive->{size
} = $newsize;
2323 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2325 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2328 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2332 __PACKAGE__-
>register_method({
2333 name
=> 'snapshot_list',
2334 path
=> '{vmid}/snapshot',
2336 description
=> "List all snapshots.",
2338 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2341 protected
=> 1, # qemu pid files are only readable by root
2343 additionalProperties
=> 0,
2345 vmid
=> get_standard_option
('pve-vmid'),
2346 node
=> get_standard_option
('pve-node'),
2355 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2360 my $vmid = $param->{vmid
};
2362 my $conf = PVE
::QemuServer
::load_config
($vmid);
2363 my $snaphash = $conf->{snapshots
} || {};
2367 foreach my $name (keys %$snaphash) {
2368 my $d = $snaphash->{$name};
2371 snaptime
=> $d->{snaptime
} || 0,
2372 vmstate
=> $d->{vmstate
} ?
1 : 0,
2373 description
=> $d->{description
} || '',
2375 $item->{parent
} = $d->{parent
} if $d->{parent
};
2376 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2380 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2381 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2382 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2384 push @$res, $current;
2389 __PACKAGE__-
>register_method({
2391 path
=> '{vmid}/snapshot',
2395 description
=> "Snapshot a VM.",
2397 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2400 additionalProperties
=> 0,
2402 node
=> get_standard_option
('pve-node'),
2403 vmid
=> get_standard_option
('pve-vmid'),
2404 snapname
=> get_standard_option
('pve-snapshot-name'),
2408 description
=> "Save the vmstate",
2413 description
=> "Freeze the filesystem",
2418 description
=> "A textual description or comment.",
2424 description
=> "the task ID.",
2429 my $rpcenv = PVE
::RPCEnvironment
::get
();
2431 my $authuser = $rpcenv->get_user();
2433 my $node = extract_param
($param, 'node');
2435 my $vmid = extract_param
($param, 'vmid');
2437 my $snapname = extract_param
($param, 'snapname');
2439 die "unable to use snapshot name 'current' (reserved name)\n"
2440 if $snapname eq 'current';
2443 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2444 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2445 $param->{freezefs
}, $param->{description
});
2448 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2451 __PACKAGE__-
>register_method({
2452 name
=> 'snapshot_cmd_idx',
2453 path
=> '{vmid}/snapshot/{snapname}',
2460 additionalProperties
=> 0,
2462 vmid
=> get_standard_option
('pve-vmid'),
2463 node
=> get_standard_option
('pve-node'),
2464 snapname
=> get_standard_option
('pve-snapshot-name'),
2473 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2480 push @$res, { cmd
=> 'rollback' };
2481 push @$res, { cmd
=> 'config' };
2486 __PACKAGE__-
>register_method({
2487 name
=> 'update_snapshot_config',
2488 path
=> '{vmid}/snapshot/{snapname}/config',
2492 description
=> "Update snapshot metadata.",
2494 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2497 additionalProperties
=> 0,
2499 node
=> get_standard_option
('pve-node'),
2500 vmid
=> get_standard_option
('pve-vmid'),
2501 snapname
=> get_standard_option
('pve-snapshot-name'),
2505 description
=> "A textual description or comment.",
2509 returns
=> { type
=> 'null' },
2513 my $rpcenv = PVE
::RPCEnvironment
::get
();
2515 my $authuser = $rpcenv->get_user();
2517 my $vmid = extract_param
($param, 'vmid');
2519 my $snapname = extract_param
($param, 'snapname');
2521 return undef if !defined($param->{description
});
2523 my $updatefn = sub {
2525 my $conf = PVE
::QemuServer
::load_config
($vmid);
2527 PVE
::QemuServer
::check_lock
($conf);
2529 my $snap = $conf->{snapshots
}->{$snapname};
2531 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2533 $snap->{description
} = $param->{description
} if defined($param->{description
});
2535 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2538 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2543 __PACKAGE__-
>register_method({
2544 name
=> 'get_snapshot_config',
2545 path
=> '{vmid}/snapshot/{snapname}/config',
2548 description
=> "Get snapshot configuration",
2550 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2553 additionalProperties
=> 0,
2555 node
=> get_standard_option
('pve-node'),
2556 vmid
=> get_standard_option
('pve-vmid'),
2557 snapname
=> get_standard_option
('pve-snapshot-name'),
2560 returns
=> { type
=> "object" },
2564 my $rpcenv = PVE
::RPCEnvironment
::get
();
2566 my $authuser = $rpcenv->get_user();
2568 my $vmid = extract_param
($param, 'vmid');
2570 my $snapname = extract_param
($param, 'snapname');
2572 my $conf = PVE
::QemuServer
::load_config
($vmid);
2574 my $snap = $conf->{snapshots
}->{$snapname};
2576 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2581 __PACKAGE__-
>register_method({
2583 path
=> '{vmid}/snapshot/{snapname}/rollback',
2587 description
=> "Rollback VM state to specified snapshot.",
2589 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2592 additionalProperties
=> 0,
2594 node
=> get_standard_option
('pve-node'),
2595 vmid
=> get_standard_option
('pve-vmid'),
2596 snapname
=> get_standard_option
('pve-snapshot-name'),
2601 description
=> "the task ID.",
2606 my $rpcenv = PVE
::RPCEnvironment
::get
();
2608 my $authuser = $rpcenv->get_user();
2610 my $node = extract_param
($param, 'node');
2612 my $vmid = extract_param
($param, 'vmid');
2614 my $snapname = extract_param
($param, 'snapname');
2617 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2618 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2621 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2624 __PACKAGE__-
>register_method({
2625 name
=> 'delsnapshot',
2626 path
=> '{vmid}/snapshot/{snapname}',
2630 description
=> "Delete a VM snapshot.",
2632 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2635 additionalProperties
=> 0,
2637 node
=> get_standard_option
('pve-node'),
2638 vmid
=> get_standard_option
('pve-vmid'),
2639 snapname
=> get_standard_option
('pve-snapshot-name'),
2643 description
=> "For removal from config file, even if removing disk snapshots fails.",
2649 description
=> "the task ID.",
2654 my $rpcenv = PVE
::RPCEnvironment
::get
();
2656 my $authuser = $rpcenv->get_user();
2658 my $node = extract_param
($param, 'node');
2660 my $vmid = extract_param
($param, 'vmid');
2662 my $snapname = extract_param
($param, 'snapname');
2665 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2666 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2669 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2672 __PACKAGE__-
>register_method({
2674 path
=> '{vmid}/template',
2678 description
=> "Create a Template.",
2680 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2681 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2684 additionalProperties
=> 0,
2686 node
=> get_standard_option
('pve-node'),
2687 vmid
=> get_standard_option
('pve-vmid'),
2691 description
=> "If you want to convert only 1 disk to base image.",
2692 enum
=> [PVE
::QemuServer
::disknames
()],
2697 returns
=> { type
=> 'null'},
2701 my $rpcenv = PVE
::RPCEnvironment
::get
();
2703 my $authuser = $rpcenv->get_user();
2705 my $node = extract_param
($param, 'node');
2707 my $vmid = extract_param
($param, 'vmid');
2709 my $disk = extract_param
($param, 'disk');
2711 my $updatefn = sub {
2713 my $conf = PVE
::QemuServer
::load_config
($vmid);
2715 PVE
::QemuServer
::check_lock
($conf);
2717 die "unable to create template, because VM contains snapshots\n"
2718 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2720 die "you can't convert a template to a template\n"
2721 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2723 die "you can't convert a VM to template if VM is running\n"
2724 if PVE
::QemuServer
::check_running
($vmid);
2727 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2730 $conf->{template
} = 1;
2731 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2733 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2736 PVE
::QemuServer
::lock_config
($vmid, $updatefn);