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 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2006 if ($param->{description
}) {
2007 $newconf->{description
} = $param->{description
};
2010 # create empty/temp config - this fails if VM already exists on other node
2011 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2016 my $newvollist = [];
2019 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2021 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2023 foreach my $opt (keys %$drives) {
2024 my $drive = $drives->{$opt};
2027 if (!$drive->{full
}) {
2028 print "create linked clone of drive $opt ($drive->{file})\n";
2029 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
2030 push @$newvollist, $newvolid;
2033 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
2034 $storeid = $storage if $storage;
2040 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
2041 $fmt = $drive->{format
} || $defformat;
2044 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
2046 print "create full clone of drive $opt ($drive->{file})\n";
2047 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
2048 push @$newvollist, $newvolid;
2050 if(!$running || $snapname){
2051 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
2053 PVE
::QemuServer
::qemu_drive_mirror
($vmid, $opt, $newvolid, $newid);
2058 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
2060 $disk->{full
} = undef;
2061 $disk->{format
} = undef;
2062 $disk->{file
} = $newvolid;
2063 $disk->{size
} = $size;
2065 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
2067 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2070 delete $newconf->{lock};
2071 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2074 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2075 die "Failed to move config to node '$target' - rename failed: $!\n"
2076 if !rename($conffile, $newconffile);
2079 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2084 sleep 1; # some storage like rbd need to wait before release volume - really?
2086 foreach my $volid (@$newvollist) {
2087 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2090 die "clone failed: $err";
2096 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2099 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2100 # Aquire exclusive lock lock for $newid
2101 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2106 __PACKAGE__-
>register_method({
2107 name
=> 'migrate_vm',
2108 path
=> '{vmid}/migrate',
2112 description
=> "Migrate virtual machine. Creates a new migration task.",
2114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2117 additionalProperties
=> 0,
2119 node
=> get_standard_option
('pve-node'),
2120 vmid
=> get_standard_option
('pve-vmid'),
2121 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2124 description
=> "Use online/live migration.",
2129 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2136 description
=> "the task ID.",
2141 my $rpcenv = PVE
::RPCEnvironment
::get
();
2143 my $authuser = $rpcenv->get_user();
2145 my $target = extract_param
($param, 'target');
2147 my $localnode = PVE
::INotify
::nodename
();
2148 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2150 PVE
::Cluster
::check_cfs_quorum
();
2152 PVE
::Cluster
::check_node_exists
($target);
2154 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2156 my $vmid = extract_param
($param, 'vmid');
2158 raise_param_exc
({ force
=> "Only root may use this option." })
2159 if $param->{force
} && $authuser ne 'root@pam';
2162 my $conf = PVE
::QemuServer
::load_config
($vmid);
2164 # try to detect errors early
2166 PVE
::QemuServer
::check_lock
($conf);
2168 if (PVE
::QemuServer
::check_running
($vmid)) {
2169 die "cant migrate running VM without --online\n"
2170 if !$param->{online
};
2173 my $storecfg = PVE
::Storage
::config
();
2174 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2176 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2181 my $service = "pvevm:$vmid";
2183 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2185 print "Executing HA migrate for VM $vmid to node $target\n";
2187 PVE
::Tools
::run_command
($cmd);
2192 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2199 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2202 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2207 __PACKAGE__-
>register_method({
2209 path
=> '{vmid}/monitor',
2213 description
=> "Execute Qemu monitor commands.",
2215 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2218 additionalProperties
=> 0,
2220 node
=> get_standard_option
('pve-node'),
2221 vmid
=> get_standard_option
('pve-vmid'),
2224 description
=> "The monitor command.",
2228 returns
=> { type
=> 'string'},
2232 my $vmid = $param->{vmid
};
2234 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2238 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2240 $res = "ERROR: $@" if $@;
2245 __PACKAGE__-
>register_method({
2246 name
=> 'resize_vm',
2247 path
=> '{vmid}/resize',
2251 description
=> "Extend volume size.",
2253 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2256 additionalProperties
=> 0,
2258 node
=> get_standard_option
('pve-node'),
2259 vmid
=> get_standard_option
('pve-vmid'),
2260 skiplock
=> get_standard_option
('skiplock'),
2263 description
=> "The disk you want to resize.",
2264 enum
=> [PVE
::QemuServer
::disknames
()],
2268 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2269 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.",
2273 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2279 returns
=> { type
=> 'null'},
2283 my $rpcenv = PVE
::RPCEnvironment
::get
();
2285 my $authuser = $rpcenv->get_user();
2287 my $node = extract_param
($param, 'node');
2289 my $vmid = extract_param
($param, 'vmid');
2291 my $digest = extract_param
($param, 'digest');
2293 my $disk = extract_param
($param, 'disk');
2295 my $sizestr = extract_param
($param, 'size');
2297 my $skiplock = extract_param
($param, 'skiplock');
2298 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2299 if $skiplock && $authuser ne 'root@pam';
2301 my $storecfg = PVE
::Storage
::config
();
2303 my $updatefn = sub {
2305 my $conf = PVE
::QemuServer
::load_config
($vmid);
2307 die "checksum missmatch (file change by other user?)\n"
2308 if $digest && $digest ne $conf->{digest
};
2309 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2311 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2313 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2315 my $volid = $drive->{file
};
2317 die "disk '$disk' has no associated volume\n" if !$volid;
2319 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2321 die "you can't online resize a virtio windows bootdisk\n"
2322 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2324 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2326 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2328 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2330 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2331 my ($ext, $newsize, $unit) = ($1, $2, $4);
2334 $newsize = $newsize * 1024;
2335 } elsif ($unit eq 'M') {
2336 $newsize = $newsize * 1024 * 1024;
2337 } elsif ($unit eq 'G') {
2338 $newsize = $newsize * 1024 * 1024 * 1024;
2339 } elsif ($unit eq 'T') {
2340 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2343 $newsize += $size if $ext;
2344 $newsize = int($newsize);
2346 die "unable to skrink disk size\n" if $newsize < $size;
2348 return if $size == $newsize;
2350 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2352 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2354 $drive->{size
} = $newsize;
2355 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2357 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2360 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2364 __PACKAGE__-
>register_method({
2365 name
=> 'snapshot_list',
2366 path
=> '{vmid}/snapshot',
2368 description
=> "List all snapshots.",
2370 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2373 protected
=> 1, # qemu pid files are only readable by root
2375 additionalProperties
=> 0,
2377 vmid
=> get_standard_option
('pve-vmid'),
2378 node
=> get_standard_option
('pve-node'),
2387 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2392 my $vmid = $param->{vmid
};
2394 my $conf = PVE
::QemuServer
::load_config
($vmid);
2395 my $snaphash = $conf->{snapshots
} || {};
2399 foreach my $name (keys %$snaphash) {
2400 my $d = $snaphash->{$name};
2403 snaptime
=> $d->{snaptime
} || 0,
2404 vmstate
=> $d->{vmstate
} ?
1 : 0,
2405 description
=> $d->{description
} || '',
2407 $item->{parent
} = $d->{parent
} if $d->{parent
};
2408 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2412 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2413 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2414 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2416 push @$res, $current;
2421 __PACKAGE__-
>register_method({
2423 path
=> '{vmid}/snapshot',
2427 description
=> "Snapshot a VM.",
2429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2432 additionalProperties
=> 0,
2434 node
=> get_standard_option
('pve-node'),
2435 vmid
=> get_standard_option
('pve-vmid'),
2436 snapname
=> get_standard_option
('pve-snapshot-name'),
2440 description
=> "Save the vmstate",
2445 description
=> "Freeze the filesystem",
2450 description
=> "A textual description or comment.",
2456 description
=> "the task ID.",
2461 my $rpcenv = PVE
::RPCEnvironment
::get
();
2463 my $authuser = $rpcenv->get_user();
2465 my $node = extract_param
($param, 'node');
2467 my $vmid = extract_param
($param, 'vmid');
2469 my $snapname = extract_param
($param, 'snapname');
2471 die "unable to use snapshot name 'current' (reserved name)\n"
2472 if $snapname eq 'current';
2475 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2476 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2477 $param->{freezefs
}, $param->{description
});
2480 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2483 __PACKAGE__-
>register_method({
2484 name
=> 'snapshot_cmd_idx',
2485 path
=> '{vmid}/snapshot/{snapname}',
2492 additionalProperties
=> 0,
2494 vmid
=> get_standard_option
('pve-vmid'),
2495 node
=> get_standard_option
('pve-node'),
2496 snapname
=> get_standard_option
('pve-snapshot-name'),
2505 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2512 push @$res, { cmd
=> 'rollback' };
2513 push @$res, { cmd
=> 'config' };
2518 __PACKAGE__-
>register_method({
2519 name
=> 'update_snapshot_config',
2520 path
=> '{vmid}/snapshot/{snapname}/config',
2524 description
=> "Update snapshot metadata.",
2526 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2529 additionalProperties
=> 0,
2531 node
=> get_standard_option
('pve-node'),
2532 vmid
=> get_standard_option
('pve-vmid'),
2533 snapname
=> get_standard_option
('pve-snapshot-name'),
2537 description
=> "A textual description or comment.",
2541 returns
=> { type
=> 'null' },
2545 my $rpcenv = PVE
::RPCEnvironment
::get
();
2547 my $authuser = $rpcenv->get_user();
2549 my $vmid = extract_param
($param, 'vmid');
2551 my $snapname = extract_param
($param, 'snapname');
2553 return undef if !defined($param->{description
});
2555 my $updatefn = sub {
2557 my $conf = PVE
::QemuServer
::load_config
($vmid);
2559 PVE
::QemuServer
::check_lock
($conf);
2561 my $snap = $conf->{snapshots
}->{$snapname};
2563 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2565 $snap->{description
} = $param->{description
} if defined($param->{description
});
2567 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2570 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2575 __PACKAGE__-
>register_method({
2576 name
=> 'get_snapshot_config',
2577 path
=> '{vmid}/snapshot/{snapname}/config',
2580 description
=> "Get snapshot configuration",
2582 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2585 additionalProperties
=> 0,
2587 node
=> get_standard_option
('pve-node'),
2588 vmid
=> get_standard_option
('pve-vmid'),
2589 snapname
=> get_standard_option
('pve-snapshot-name'),
2592 returns
=> { type
=> "object" },
2596 my $rpcenv = PVE
::RPCEnvironment
::get
();
2598 my $authuser = $rpcenv->get_user();
2600 my $vmid = extract_param
($param, 'vmid');
2602 my $snapname = extract_param
($param, 'snapname');
2604 my $conf = PVE
::QemuServer
::load_config
($vmid);
2606 my $snap = $conf->{snapshots
}->{$snapname};
2608 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2613 __PACKAGE__-
>register_method({
2615 path
=> '{vmid}/snapshot/{snapname}/rollback',
2619 description
=> "Rollback VM state to specified snapshot.",
2621 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2624 additionalProperties
=> 0,
2626 node
=> get_standard_option
('pve-node'),
2627 vmid
=> get_standard_option
('pve-vmid'),
2628 snapname
=> get_standard_option
('pve-snapshot-name'),
2633 description
=> "the task ID.",
2638 my $rpcenv = PVE
::RPCEnvironment
::get
();
2640 my $authuser = $rpcenv->get_user();
2642 my $node = extract_param
($param, 'node');
2644 my $vmid = extract_param
($param, 'vmid');
2646 my $snapname = extract_param
($param, 'snapname');
2649 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2650 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2653 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2656 __PACKAGE__-
>register_method({
2657 name
=> 'delsnapshot',
2658 path
=> '{vmid}/snapshot/{snapname}',
2662 description
=> "Delete a VM snapshot.",
2664 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2667 additionalProperties
=> 0,
2669 node
=> get_standard_option
('pve-node'),
2670 vmid
=> get_standard_option
('pve-vmid'),
2671 snapname
=> get_standard_option
('pve-snapshot-name'),
2675 description
=> "For removal from config file, even if removing disk snapshots fails.",
2681 description
=> "the task ID.",
2686 my $rpcenv = PVE
::RPCEnvironment
::get
();
2688 my $authuser = $rpcenv->get_user();
2690 my $node = extract_param
($param, 'node');
2692 my $vmid = extract_param
($param, 'vmid');
2694 my $snapname = extract_param
($param, 'snapname');
2697 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2698 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2701 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2704 __PACKAGE__-
>register_method({
2706 path
=> '{vmid}/template',
2710 description
=> "Create a Template.",
2712 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2713 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2716 additionalProperties
=> 0,
2718 node
=> get_standard_option
('pve-node'),
2719 vmid
=> get_standard_option
('pve-vmid'),
2723 description
=> "If you want to convert only 1 disk to base image.",
2724 enum
=> [PVE
::QemuServer
::disknames
()],
2729 returns
=> { type
=> 'null'},
2733 my $rpcenv = PVE
::RPCEnvironment
::get
();
2735 my $authuser = $rpcenv->get_user();
2737 my $node = extract_param
($param, 'node');
2739 my $vmid = extract_param
($param, 'vmid');
2741 my $disk = extract_param
($param, 'disk');
2743 my $updatefn = sub {
2745 my $conf = PVE
::QemuServer
::load_config
($vmid);
2747 PVE
::QemuServer
::check_lock
($conf);
2749 die "unable to create template, because VM contains snapshots\n"
2750 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2752 die "you can't convert a template to a template\n"
2753 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2755 die "you can't convert a VM to template if VM is running\n"
2756 if PVE
::QemuServer
::check_running
($vmid);
2759 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2762 $conf->{template
} = 1;
2763 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2765 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2768 PVE
::QemuServer
::lock_config
($vmid, $updatefn);