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
};
663 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
664 if ($force || $key =~ m/^unused/) {
665 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
668 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
673 delete $conf->{$key};
676 my $vmconfig_delete_option = sub {
677 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
679 return if !defined($conf->{$opt});
681 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
684 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
686 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
687 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
688 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
692 my $unplugwarning = "";
693 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
694 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
695 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
696 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
697 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
698 $unplugwarning = "<br>verify that your guest support acpi hotplug";
701 if($opt eq 'tablet'){
702 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
704 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
708 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
709 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
711 delete $conf->{$opt};
714 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
717 my $safe_num_ne = sub {
720 return 0 if !defined($a) && !defined($b);
721 return 1 if !defined($a);
722 return 1 if !defined($b);
727 my $vmconfig_update_disk = sub {
728 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
730 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
732 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
733 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
735 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
740 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
742 my $media = $drive->{media
} || 'disk';
743 my $oldmedia = $old_drive->{media
} || 'disk';
744 die "unable to change media type\n" if $media ne $oldmedia;
746 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
747 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
749 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
750 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
753 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
754 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
755 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
756 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
757 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
758 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
759 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
760 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
761 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
762 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
767 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
768 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
770 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
771 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
773 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
775 if (PVE
::QemuServer
::check_running
($vmid)) {
776 if ($drive->{file
} eq 'none') {
777 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
779 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
780 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
781 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
785 } else { # hotplug new disks
787 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
791 my $vmconfig_update_net = sub {
792 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
794 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
795 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
796 my $newnet = PVE
::QemuServer
::parse_net
($value);
798 if($oldnet->{model
} ne $newnet->{model
}){
799 #if model change, we try to hot-unplug
800 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
803 if($newnet->{bridge
} && $oldnet->{bridge
}){
804 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
806 if($newnet->{rate
} ne $oldnet->{rate
}){
807 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
810 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
811 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
812 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
816 #if bridge/nat mode change, we try to hot-unplug
817 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
822 $conf->{$opt} = $value;
823 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
824 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
826 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
828 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
831 my $vm_config_perm_list = [
841 __PACKAGE__-
>register_method({
843 path
=> '{vmid}/config',
847 description
=> "Set virtual machine options.",
849 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
852 additionalProperties
=> 0,
853 properties
=> PVE
::QemuServer
::json_config_properties
(
855 node
=> get_standard_option
('pve-node'),
856 vmid
=> get_standard_option
('pve-vmid'),
857 skiplock
=> get_standard_option
('skiplock'),
859 type
=> 'string', format
=> 'pve-configid-list',
860 description
=> "A list of settings you want to delete.",
865 description
=> $opt_force_description,
867 requires
=> 'delete',
871 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
877 returns
=> { type
=> 'null'},
881 my $rpcenv = PVE
::RPCEnvironment
::get
();
883 my $authuser = $rpcenv->get_user();
885 my $node = extract_param
($param, 'node');
887 my $vmid = extract_param
($param, 'vmid');
889 my $digest = extract_param
($param, 'digest');
891 my @paramarr = (); # used for log message
892 foreach my $key (keys %$param) {
893 push @paramarr, "-$key", $param->{$key};
896 my $skiplock = extract_param
($param, 'skiplock');
897 raise_param_exc
({ skiplock
=> "Only root may use this option." })
898 if $skiplock && $authuser ne 'root@pam';
900 my $delete_str = extract_param
($param, 'delete');
902 my $force = extract_param
($param, 'force');
904 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
906 my $storecfg = PVE
::Storage
::config
();
908 my $defaults = PVE
::QemuServer
::load_defaults
();
910 &$resolve_cdrom_alias($param);
912 # now try to verify all parameters
915 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
916 $opt = 'ide2' if $opt eq 'cdrom';
917 raise_param_exc
({ delete => "you can't use '-$opt' and " .
918 "-delete $opt' at the same time" })
919 if defined($param->{$opt});
921 if (!PVE
::QemuServer
::option_exists
($opt)) {
922 raise_param_exc
({ delete => "unknown option '$opt'" });
928 foreach my $opt (keys %$param) {
929 if (PVE
::QemuServer
::valid_drivename
($opt)) {
931 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
932 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
933 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
934 } elsif ($opt =~ m/^net(\d+)$/) {
936 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
937 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
941 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
943 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
945 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
949 my $conf = PVE
::QemuServer
::load_config
($vmid);
951 die "checksum missmatch (file change by other user?)\n"
952 if $digest && $digest ne $conf->{digest
};
954 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
956 if ($param->{memory
} || defined($param->{balloon
})) {
957 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
958 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
960 die "balloon value too large (must be smaller than assigned memory)\n"
961 if $balloon > $maxmem;
964 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
966 foreach my $opt (@delete) { # delete
967 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
968 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
971 my $running = PVE
::QemuServer
::check_running
($vmid);
973 foreach my $opt (keys %$param) { # add/change
975 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
977 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
979 if (PVE
::QemuServer
::valid_drivename
($opt)) {
981 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
982 $opt, $param->{$opt}, $force);
984 } elsif ($opt =~ m/^net(\d+)$/) { #nics
986 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
987 $opt, $param->{$opt});
991 if($opt eq 'tablet' && $param->{$opt} == 1){
992 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
993 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
994 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
997 $conf->{$opt} = $param->{$opt};
998 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1002 # allow manual ballooning if shares is set to zero
1003 if ($running && defined($param->{balloon
}) &&
1004 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1005 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1006 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1011 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1017 __PACKAGE__-
>register_method({
1018 name
=> 'destroy_vm',
1023 description
=> "Destroy the vm (also delete all used/owned volumes).",
1025 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1028 additionalProperties
=> 0,
1030 node
=> get_standard_option
('pve-node'),
1031 vmid
=> get_standard_option
('pve-vmid'),
1032 skiplock
=> get_standard_option
('skiplock'),
1041 my $rpcenv = PVE
::RPCEnvironment
::get
();
1043 my $authuser = $rpcenv->get_user();
1045 my $vmid = $param->{vmid
};
1047 my $skiplock = $param->{skiplock
};
1048 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1049 if $skiplock && $authuser ne 'root@pam';
1052 my $conf = PVE
::QemuServer
::load_config
($vmid);
1054 my $storecfg = PVE
::Storage
::config
();
1056 my $delVMfromPoolFn = sub {
1057 my $usercfg = cfs_read_file
("user.cfg");
1058 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1059 if (my $data = $usercfg->{pools
}->{$pool}) {
1060 delete $data->{vms
}->{$vmid};
1061 delete $usercfg->{vms
}->{$vmid};
1062 cfs_write_file
("user.cfg", $usercfg);
1070 syslog
('info', "destroy VM $vmid: $upid\n");
1072 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1074 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1077 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1080 __PACKAGE__-
>register_method({
1082 path
=> '{vmid}/unlink',
1086 description
=> "Unlink/delete disk images.",
1088 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1091 additionalProperties
=> 0,
1093 node
=> get_standard_option
('pve-node'),
1094 vmid
=> get_standard_option
('pve-vmid'),
1096 type
=> 'string', format
=> 'pve-configid-list',
1097 description
=> "A list of disk IDs you want to delete.",
1101 description
=> $opt_force_description,
1106 returns
=> { type
=> 'null'},
1110 $param->{delete} = extract_param
($param, 'idlist');
1112 __PACKAGE__-
>update_vm($param);
1119 __PACKAGE__-
>register_method({
1121 path
=> '{vmid}/vncproxy',
1125 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1127 description
=> "Creates a TCP VNC proxy connections.",
1129 additionalProperties
=> 0,
1131 node
=> get_standard_option
('pve-node'),
1132 vmid
=> get_standard_option
('pve-vmid'),
1136 additionalProperties
=> 0,
1138 user
=> { type
=> 'string' },
1139 ticket
=> { type
=> 'string' },
1140 cert
=> { type
=> 'string' },
1141 port
=> { type
=> 'integer' },
1142 upid
=> { type
=> 'string' },
1148 my $rpcenv = PVE
::RPCEnvironment
::get
();
1150 my $authuser = $rpcenv->get_user();
1152 my $vmid = $param->{vmid
};
1153 my $node = $param->{node
};
1155 my $authpath = "/vms/$vmid";
1157 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1159 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1162 my $port = PVE
::Tools
::next_vnc_port
();
1166 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1167 $remip = PVE
::Cluster
::remote_node_ip
($node);
1170 # NOTE: kvm VNC traffic is already TLS encrypted
1171 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1178 syslog
('info', "starting vnc proxy $upid\n");
1180 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1182 my $qmstr = join(' ', @$qmcmd);
1184 # also redirect stderr (else we get RFB protocol errors)
1185 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1187 PVE
::Tools
::run_command
($cmd);
1192 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1194 PVE
::Tools
::wait_for_vnc_port
($port);
1205 __PACKAGE__-
>register_method({
1207 path
=> '{vmid}/status',
1210 description
=> "Directory index",
1215 additionalProperties
=> 0,
1217 node
=> get_standard_option
('pve-node'),
1218 vmid
=> get_standard_option
('pve-vmid'),
1226 subdir
=> { type
=> 'string' },
1229 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1235 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1238 { subdir
=> 'current' },
1239 { subdir
=> 'start' },
1240 { subdir
=> 'stop' },
1246 my $vm_is_ha_managed = sub {
1249 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1250 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1256 __PACKAGE__-
>register_method({
1257 name
=> 'vm_status',
1258 path
=> '{vmid}/status/current',
1261 protected
=> 1, # qemu pid files are only readable by root
1262 description
=> "Get virtual machine status.",
1264 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1267 additionalProperties
=> 0,
1269 node
=> get_standard_option
('pve-node'),
1270 vmid
=> get_standard_option
('pve-vmid'),
1273 returns
=> { type
=> 'object' },
1278 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1280 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1281 my $status = $vmstatus->{$param->{vmid
}};
1283 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1288 __PACKAGE__-
>register_method({
1290 path
=> '{vmid}/status/start',
1294 description
=> "Start virtual machine.",
1296 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1299 additionalProperties
=> 0,
1301 node
=> get_standard_option
('pve-node'),
1302 vmid
=> get_standard_option
('pve-vmid'),
1303 skiplock
=> get_standard_option
('skiplock'),
1304 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1305 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1315 my $rpcenv = PVE
::RPCEnvironment
::get
();
1317 my $authuser = $rpcenv->get_user();
1319 my $node = extract_param
($param, 'node');
1321 my $vmid = extract_param
($param, 'vmid');
1323 my $stateuri = extract_param
($param, 'stateuri');
1324 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1325 if $stateuri && $authuser ne 'root@pam';
1327 my $skiplock = extract_param
($param, 'skiplock');
1328 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1329 if $skiplock && $authuser ne 'root@pam';
1331 my $migratedfrom = extract_param
($param, 'migratedfrom');
1332 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1333 if $migratedfrom && $authuser ne 'root@pam';
1335 my $storecfg = PVE
::Storage
::config
();
1337 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1338 $rpcenv->{type
} ne 'ha') {
1343 my $service = "pvevm:$vmid";
1345 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1347 print "Executing HA start for VM $vmid\n";
1349 PVE
::Tools
::run_command
($cmd);
1354 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1361 syslog
('info', "start VM $vmid: $upid\n");
1363 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1368 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1372 __PACKAGE__-
>register_method({
1374 path
=> '{vmid}/status/stop',
1378 description
=> "Stop virtual machine.",
1380 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1383 additionalProperties
=> 0,
1385 node
=> get_standard_option
('pve-node'),
1386 vmid
=> get_standard_option
('pve-vmid'),
1387 skiplock
=> get_standard_option
('skiplock'),
1388 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1390 description
=> "Wait maximal timeout seconds.",
1396 description
=> "Do not decativate storage volumes.",
1409 my $rpcenv = PVE
::RPCEnvironment
::get
();
1411 my $authuser = $rpcenv->get_user();
1413 my $node = extract_param
($param, 'node');
1415 my $vmid = extract_param
($param, 'vmid');
1417 my $skiplock = extract_param
($param, 'skiplock');
1418 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1419 if $skiplock && $authuser ne 'root@pam';
1421 my $keepActive = extract_param
($param, 'keepActive');
1422 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1423 if $keepActive && $authuser ne 'root@pam';
1425 my $migratedfrom = extract_param
($param, 'migratedfrom');
1426 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1427 if $migratedfrom && $authuser ne 'root@pam';
1430 my $storecfg = PVE
::Storage
::config
();
1432 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1437 my $service = "pvevm:$vmid";
1439 my $cmd = ['clusvcadm', '-d', $service];
1441 print "Executing HA stop for VM $vmid\n";
1443 PVE
::Tools
::run_command
($cmd);
1448 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1454 syslog
('info', "stop VM $vmid: $upid\n");
1456 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1457 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1462 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1466 __PACKAGE__-
>register_method({
1468 path
=> '{vmid}/status/reset',
1472 description
=> "Reset virtual machine.",
1474 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1477 additionalProperties
=> 0,
1479 node
=> get_standard_option
('pve-node'),
1480 vmid
=> get_standard_option
('pve-vmid'),
1481 skiplock
=> get_standard_option
('skiplock'),
1490 my $rpcenv = PVE
::RPCEnvironment
::get
();
1492 my $authuser = $rpcenv->get_user();
1494 my $node = extract_param
($param, 'node');
1496 my $vmid = extract_param
($param, 'vmid');
1498 my $skiplock = extract_param
($param, 'skiplock');
1499 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1500 if $skiplock && $authuser ne 'root@pam';
1502 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1507 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1512 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1515 __PACKAGE__-
>register_method({
1516 name
=> 'vm_shutdown',
1517 path
=> '{vmid}/status/shutdown',
1521 description
=> "Shutdown virtual machine.",
1523 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1526 additionalProperties
=> 0,
1528 node
=> get_standard_option
('pve-node'),
1529 vmid
=> get_standard_option
('pve-vmid'),
1530 skiplock
=> get_standard_option
('skiplock'),
1532 description
=> "Wait maximal timeout seconds.",
1538 description
=> "Make sure the VM stops.",
1544 description
=> "Do not decativate storage volumes.",
1557 my $rpcenv = PVE
::RPCEnvironment
::get
();
1559 my $authuser = $rpcenv->get_user();
1561 my $node = extract_param
($param, 'node');
1563 my $vmid = extract_param
($param, 'vmid');
1565 my $skiplock = extract_param
($param, 'skiplock');
1566 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1567 if $skiplock && $authuser ne 'root@pam';
1569 my $keepActive = extract_param
($param, 'keepActive');
1570 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1571 if $keepActive && $authuser ne 'root@pam';
1573 my $storecfg = PVE
::Storage
::config
();
1578 syslog
('info', "shutdown VM $vmid: $upid\n");
1580 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1581 1, $param->{forceStop
}, $keepActive);
1586 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1589 __PACKAGE__-
>register_method({
1590 name
=> 'vm_suspend',
1591 path
=> '{vmid}/status/suspend',
1595 description
=> "Suspend virtual machine.",
1597 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1600 additionalProperties
=> 0,
1602 node
=> get_standard_option
('pve-node'),
1603 vmid
=> get_standard_option
('pve-vmid'),
1604 skiplock
=> get_standard_option
('skiplock'),
1613 my $rpcenv = PVE
::RPCEnvironment
::get
();
1615 my $authuser = $rpcenv->get_user();
1617 my $node = extract_param
($param, 'node');
1619 my $vmid = extract_param
($param, 'vmid');
1621 my $skiplock = extract_param
($param, 'skiplock');
1622 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1623 if $skiplock && $authuser ne 'root@pam';
1625 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1630 syslog
('info', "suspend VM $vmid: $upid\n");
1632 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1637 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1640 __PACKAGE__-
>register_method({
1641 name
=> 'vm_resume',
1642 path
=> '{vmid}/status/resume',
1646 description
=> "Resume virtual machine.",
1648 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1651 additionalProperties
=> 0,
1653 node
=> get_standard_option
('pve-node'),
1654 vmid
=> get_standard_option
('pve-vmid'),
1655 skiplock
=> get_standard_option
('skiplock'),
1664 my $rpcenv = PVE
::RPCEnvironment
::get
();
1666 my $authuser = $rpcenv->get_user();
1668 my $node = extract_param
($param, 'node');
1670 my $vmid = extract_param
($param, 'vmid');
1672 my $skiplock = extract_param
($param, 'skiplock');
1673 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1674 if $skiplock && $authuser ne 'root@pam';
1676 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1681 syslog
('info', "resume VM $vmid: $upid\n");
1683 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1688 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1691 __PACKAGE__-
>register_method({
1692 name
=> 'vm_sendkey',
1693 path
=> '{vmid}/sendkey',
1697 description
=> "Send key event to virtual machine.",
1699 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1702 additionalProperties
=> 0,
1704 node
=> get_standard_option
('pve-node'),
1705 vmid
=> get_standard_option
('pve-vmid'),
1706 skiplock
=> get_standard_option
('skiplock'),
1708 description
=> "The key (qemu monitor encoding).",
1713 returns
=> { type
=> 'null'},
1717 my $rpcenv = PVE
::RPCEnvironment
::get
();
1719 my $authuser = $rpcenv->get_user();
1721 my $node = extract_param
($param, 'node');
1723 my $vmid = extract_param
($param, 'vmid');
1725 my $skiplock = extract_param
($param, 'skiplock');
1726 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1727 if $skiplock && $authuser ne 'root@pam';
1729 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1734 __PACKAGE__-
>register_method({
1735 name
=> 'vm_feature',
1736 path
=> '{vmid}/feature',
1740 description
=> "Check if feature for virtual machine is available.",
1742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1745 additionalProperties
=> 0,
1747 node
=> get_standard_option
('pve-node'),
1748 vmid
=> get_standard_option
('pve-vmid'),
1750 description
=> "Feature to check.",
1752 enum
=> [ 'snapshot', 'clone', 'copy' ],
1754 snapname
=> get_standard_option
('pve-snapshot-name', {
1762 hasFeature
=> { type
=> 'boolean' },
1765 items
=> { type
=> 'string' },
1772 my $node = extract_param
($param, 'node');
1774 my $vmid = extract_param
($param, 'vmid');
1776 my $snapname = extract_param
($param, 'snapname');
1778 my $feature = extract_param
($param, 'feature');
1780 my $running = PVE
::QemuServer
::check_running
($vmid);
1782 my $conf = PVE
::QemuServer
::load_config
($vmid);
1785 my $snap = $conf->{snapshots
}->{$snapname};
1786 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1789 my $storecfg = PVE
::Storage
::config
();
1791 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1792 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1795 hasFeature
=> $hasFeature,
1796 nodes
=> [ keys %$nodelist ],
1800 __PACKAGE__-
>register_method({
1802 path
=> '{vmid}/clone',
1806 description
=> "Create a copy of virtual machine/template.",
1808 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1809 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1810 "'Datastore.AllocateSpace' on any used storage.",
1813 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1815 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1816 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1821 additionalProperties
=> 0,
1823 node
=> get_standard_option
('pve-node'),
1824 vmid
=> get_standard_option
('pve-vmid'),
1825 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1828 type
=> 'string', format
=> 'dns-name',
1829 description
=> "Set a name for the new VM.",
1834 description
=> "Description for the new VM.",
1838 type
=> 'string', format
=> 'pve-poolid',
1839 description
=> "Add the new VM to the specified pool.",
1841 snapname
=> get_standard_option
('pve-snapshot-name', {
1845 storage
=> get_standard_option
('pve-storage-id', {
1846 description
=> "Target storage for full clone.",
1851 description
=> "Target format for file storage.",
1855 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1860 description
=> "Create a full copy of all disk. This is always done when " .
1861 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1864 target
=> get_standard_option
('pve-node', {
1865 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1876 my $rpcenv = PVE
::RPCEnvironment
::get
();
1878 my $authuser = $rpcenv->get_user();
1880 my $node = extract_param
($param, 'node');
1882 my $vmid = extract_param
($param, 'vmid');
1884 my $newid = extract_param
($param, 'newid');
1886 my $pool = extract_param
($param, 'pool');
1888 if (defined($pool)) {
1889 $rpcenv->check_pool_exist($pool);
1892 my $snapname = extract_param
($param, 'snapname');
1894 my $storage = extract_param
($param, 'storage');
1896 my $format = extract_param
($param, 'format');
1898 my $target = extract_param
($param, 'target');
1900 my $localnode = PVE
::INotify
::nodename
();
1902 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1904 PVE
::Cluster
::check_node_exists
($target) if $target;
1906 my $storecfg = PVE
::Storage
::config
();
1909 # check if storage is enabled on local node
1910 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1912 # check if storage is available on target node
1913 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1914 # clone only works if target storage is shared
1915 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1916 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1920 PVE
::Cluster
::check_cfs_quorum
();
1922 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1924 # exclusive lock if VM is running - else shared lock is enough;
1925 my $shared_lock = $running ?
0 : 1;
1929 # do all tests after lock
1930 # we also try to do all tests before we fork the worker
1932 my $conf = PVE
::QemuServer
::load_config
($vmid);
1934 PVE
::QemuServer
::check_lock
($conf);
1936 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1938 die "unexpected state change\n" if $verify_running != $running;
1940 die "snapshot '$snapname' does not exist\n"
1941 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1943 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1945 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1947 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1949 my $conffile = PVE
::QemuServer
::config_file
($newid);
1951 die "unable to create VM $newid: config file already exists\n"
1954 my $newconf = { lock => 'clone' };
1958 foreach my $opt (keys %$oldconf) {
1959 my $value = $oldconf->{$opt};
1961 # do not copy snapshot related info
1962 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1963 $opt eq 'vmstate' || $opt eq 'snapstate';
1965 # always change MAC! address
1966 if ($opt =~ m/^net(\d+)$/) {
1967 my $net = PVE
::QemuServer
::parse_net
($value);
1968 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1969 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1970 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1971 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1972 $newconf->{$opt} = $value; # simply copy configuration
1974 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1975 die "Full clone feature is not available"
1976 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
1979 $drives->{$opt} = $drive;
1980 push @$vollist, $drive->{file
};
1983 # copy everything else
1984 $newconf->{$opt} = $value;
1988 delete $newconf->{template
};
1990 if ($param->{name
}) {
1991 $newconf->{name
} = $param->{name
};
1993 $newconf->{name
} = "Copy-of-$oldconf->{name}";
1996 if ($param->{description
}) {
1997 $newconf->{description
} = $param->{description
};
2000 # create empty/temp config - this fails if VM already exists on other node
2001 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2006 my $newvollist = [];
2009 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2011 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2013 foreach my $opt (keys %$drives) {
2014 my $drive = $drives->{$opt};
2017 if (!$drive->{full
}) {
2018 print "create linked clone of drive $opt ($drive->{file})\n";
2019 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
2020 push @$newvollist, $newvolid;
2023 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
2024 $storeid = $storage if $storage;
2030 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
2031 $fmt = $drive->{format
} || $defformat;
2034 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
2036 print "create full clone of drive $opt ($drive->{file})\n";
2037 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
2038 push @$newvollist, $newvolid;
2040 if(!$running || $snapname){
2041 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
2043 PVE
::QemuServer
::qemu_drive_mirror
($vmid, $opt, $newvolid, $newid);
2048 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
2050 $disk->{full
} = undef;
2051 $disk->{format
} = undef;
2052 $disk->{file
} = $newvolid;
2053 $disk->{size
} = $size;
2055 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
2057 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2060 delete $newconf->{lock};
2061 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2064 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2065 die "Failed to move config to node '$target' - rename failed: $!\n"
2066 if !rename($conffile, $newconffile);
2069 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2074 sleep 1; # some storage like rbd need to wait before release volume - really?
2076 foreach my $volid (@$newvollist) {
2077 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2080 die "clone failed: $err";
2086 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2089 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2090 # Aquire exclusive lock lock for $newid
2091 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2096 __PACKAGE__-
>register_method({
2097 name
=> 'migrate_vm',
2098 path
=> '{vmid}/migrate',
2102 description
=> "Migrate virtual machine. Creates a new migration task.",
2104 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2107 additionalProperties
=> 0,
2109 node
=> get_standard_option
('pve-node'),
2110 vmid
=> get_standard_option
('pve-vmid'),
2111 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2114 description
=> "Use online/live migration.",
2119 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2126 description
=> "the task ID.",
2131 my $rpcenv = PVE
::RPCEnvironment
::get
();
2133 my $authuser = $rpcenv->get_user();
2135 my $target = extract_param
($param, 'target');
2137 my $localnode = PVE
::INotify
::nodename
();
2138 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2140 PVE
::Cluster
::check_cfs_quorum
();
2142 PVE
::Cluster
::check_node_exists
($target);
2144 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2146 my $vmid = extract_param
($param, 'vmid');
2148 raise_param_exc
({ force
=> "Only root may use this option." })
2149 if $param->{force
} && $authuser ne 'root@pam';
2152 my $conf = PVE
::QemuServer
::load_config
($vmid);
2154 # try to detect errors early
2156 PVE
::QemuServer
::check_lock
($conf);
2158 if (PVE
::QemuServer
::check_running
($vmid)) {
2159 die "cant migrate running VM without --online\n"
2160 if !$param->{online
};
2163 my $storecfg = PVE
::Storage
::config
();
2164 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2166 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2171 my $service = "pvevm:$vmid";
2173 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2175 print "Executing HA migrate for VM $vmid to node $target\n";
2177 PVE
::Tools
::run_command
($cmd);
2182 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2189 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2192 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2197 __PACKAGE__-
>register_method({
2199 path
=> '{vmid}/monitor',
2203 description
=> "Execute Qemu monitor commands.",
2205 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2208 additionalProperties
=> 0,
2210 node
=> get_standard_option
('pve-node'),
2211 vmid
=> get_standard_option
('pve-vmid'),
2214 description
=> "The monitor command.",
2218 returns
=> { type
=> 'string'},
2222 my $vmid = $param->{vmid
};
2224 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2228 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2230 $res = "ERROR: $@" if $@;
2235 __PACKAGE__-
>register_method({
2236 name
=> 'resize_vm',
2237 path
=> '{vmid}/resize',
2241 description
=> "Extend volume size.",
2243 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2246 additionalProperties
=> 0,
2248 node
=> get_standard_option
('pve-node'),
2249 vmid
=> get_standard_option
('pve-vmid'),
2250 skiplock
=> get_standard_option
('skiplock'),
2253 description
=> "The disk you want to resize.",
2254 enum
=> [PVE
::QemuServer
::disknames
()],
2258 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2259 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.",
2263 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2269 returns
=> { type
=> 'null'},
2273 my $rpcenv = PVE
::RPCEnvironment
::get
();
2275 my $authuser = $rpcenv->get_user();
2277 my $node = extract_param
($param, 'node');
2279 my $vmid = extract_param
($param, 'vmid');
2281 my $digest = extract_param
($param, 'digest');
2283 my $disk = extract_param
($param, 'disk');
2285 my $sizestr = extract_param
($param, 'size');
2287 my $skiplock = extract_param
($param, 'skiplock');
2288 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2289 if $skiplock && $authuser ne 'root@pam';
2291 my $storecfg = PVE
::Storage
::config
();
2293 my $updatefn = sub {
2295 my $conf = PVE
::QemuServer
::load_config
($vmid);
2297 die "checksum missmatch (file change by other user?)\n"
2298 if $digest && $digest ne $conf->{digest
};
2299 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2301 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2303 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2305 my $volid = $drive->{file
};
2307 die "disk '$disk' has no associated volume\n" if !$volid;
2309 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2311 die "you can't online resize a virtio windows bootdisk\n"
2312 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2314 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2316 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2318 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2320 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2321 my ($ext, $newsize, $unit) = ($1, $2, $4);
2324 $newsize = $newsize * 1024;
2325 } elsif ($unit eq 'M') {
2326 $newsize = $newsize * 1024 * 1024;
2327 } elsif ($unit eq 'G') {
2328 $newsize = $newsize * 1024 * 1024 * 1024;
2329 } elsif ($unit eq 'T') {
2330 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2333 $newsize += $size if $ext;
2334 $newsize = int($newsize);
2336 die "unable to skrink disk size\n" if $newsize < $size;
2338 return if $size == $newsize;
2340 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2342 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2344 $drive->{size
} = $newsize;
2345 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2347 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2350 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2354 __PACKAGE__-
>register_method({
2355 name
=> 'snapshot_list',
2356 path
=> '{vmid}/snapshot',
2358 description
=> "List all snapshots.",
2360 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2363 protected
=> 1, # qemu pid files are only readable by root
2365 additionalProperties
=> 0,
2367 vmid
=> get_standard_option
('pve-vmid'),
2368 node
=> get_standard_option
('pve-node'),
2377 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2382 my $vmid = $param->{vmid
};
2384 my $conf = PVE
::QemuServer
::load_config
($vmid);
2385 my $snaphash = $conf->{snapshots
} || {};
2389 foreach my $name (keys %$snaphash) {
2390 my $d = $snaphash->{$name};
2393 snaptime
=> $d->{snaptime
} || 0,
2394 vmstate
=> $d->{vmstate
} ?
1 : 0,
2395 description
=> $d->{description
} || '',
2397 $item->{parent
} = $d->{parent
} if $d->{parent
};
2398 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2402 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2403 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2404 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2406 push @$res, $current;
2411 __PACKAGE__-
>register_method({
2413 path
=> '{vmid}/snapshot',
2417 description
=> "Snapshot a VM.",
2419 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2422 additionalProperties
=> 0,
2424 node
=> get_standard_option
('pve-node'),
2425 vmid
=> get_standard_option
('pve-vmid'),
2426 snapname
=> get_standard_option
('pve-snapshot-name'),
2430 description
=> "Save the vmstate",
2435 description
=> "Freeze the filesystem",
2440 description
=> "A textual description or comment.",
2446 description
=> "the task ID.",
2451 my $rpcenv = PVE
::RPCEnvironment
::get
();
2453 my $authuser = $rpcenv->get_user();
2455 my $node = extract_param
($param, 'node');
2457 my $vmid = extract_param
($param, 'vmid');
2459 my $snapname = extract_param
($param, 'snapname');
2461 die "unable to use snapshot name 'current' (reserved name)\n"
2462 if $snapname eq 'current';
2465 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2466 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2467 $param->{freezefs
}, $param->{description
});
2470 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2473 __PACKAGE__-
>register_method({
2474 name
=> 'snapshot_cmd_idx',
2475 path
=> '{vmid}/snapshot/{snapname}',
2482 additionalProperties
=> 0,
2484 vmid
=> get_standard_option
('pve-vmid'),
2485 node
=> get_standard_option
('pve-node'),
2486 snapname
=> get_standard_option
('pve-snapshot-name'),
2495 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2502 push @$res, { cmd
=> 'rollback' };
2503 push @$res, { cmd
=> 'config' };
2508 __PACKAGE__-
>register_method({
2509 name
=> 'update_snapshot_config',
2510 path
=> '{vmid}/snapshot/{snapname}/config',
2514 description
=> "Update snapshot metadata.",
2516 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2519 additionalProperties
=> 0,
2521 node
=> get_standard_option
('pve-node'),
2522 vmid
=> get_standard_option
('pve-vmid'),
2523 snapname
=> get_standard_option
('pve-snapshot-name'),
2527 description
=> "A textual description or comment.",
2531 returns
=> { type
=> 'null' },
2535 my $rpcenv = PVE
::RPCEnvironment
::get
();
2537 my $authuser = $rpcenv->get_user();
2539 my $vmid = extract_param
($param, 'vmid');
2541 my $snapname = extract_param
($param, 'snapname');
2543 return undef if !defined($param->{description
});
2545 my $updatefn = sub {
2547 my $conf = PVE
::QemuServer
::load_config
($vmid);
2549 PVE
::QemuServer
::check_lock
($conf);
2551 my $snap = $conf->{snapshots
}->{$snapname};
2553 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2555 $snap->{description
} = $param->{description
} if defined($param->{description
});
2557 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2560 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2565 __PACKAGE__-
>register_method({
2566 name
=> 'get_snapshot_config',
2567 path
=> '{vmid}/snapshot/{snapname}/config',
2570 description
=> "Get snapshot configuration",
2572 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2575 additionalProperties
=> 0,
2577 node
=> get_standard_option
('pve-node'),
2578 vmid
=> get_standard_option
('pve-vmid'),
2579 snapname
=> get_standard_option
('pve-snapshot-name'),
2582 returns
=> { type
=> "object" },
2586 my $rpcenv = PVE
::RPCEnvironment
::get
();
2588 my $authuser = $rpcenv->get_user();
2590 my $vmid = extract_param
($param, 'vmid');
2592 my $snapname = extract_param
($param, 'snapname');
2594 my $conf = PVE
::QemuServer
::load_config
($vmid);
2596 my $snap = $conf->{snapshots
}->{$snapname};
2598 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2603 __PACKAGE__-
>register_method({
2605 path
=> '{vmid}/snapshot/{snapname}/rollback',
2609 description
=> "Rollback VM state to specified snapshot.",
2611 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2614 additionalProperties
=> 0,
2616 node
=> get_standard_option
('pve-node'),
2617 vmid
=> get_standard_option
('pve-vmid'),
2618 snapname
=> get_standard_option
('pve-snapshot-name'),
2623 description
=> "the task ID.",
2628 my $rpcenv = PVE
::RPCEnvironment
::get
();
2630 my $authuser = $rpcenv->get_user();
2632 my $node = extract_param
($param, 'node');
2634 my $vmid = extract_param
($param, 'vmid');
2636 my $snapname = extract_param
($param, 'snapname');
2639 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2640 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2643 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2646 __PACKAGE__-
>register_method({
2647 name
=> 'delsnapshot',
2648 path
=> '{vmid}/snapshot/{snapname}',
2652 description
=> "Delete a VM snapshot.",
2654 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2657 additionalProperties
=> 0,
2659 node
=> get_standard_option
('pve-node'),
2660 vmid
=> get_standard_option
('pve-vmid'),
2661 snapname
=> get_standard_option
('pve-snapshot-name'),
2665 description
=> "For removal from config file, even if removing disk snapshots fails.",
2671 description
=> "the task ID.",
2676 my $rpcenv = PVE
::RPCEnvironment
::get
();
2678 my $authuser = $rpcenv->get_user();
2680 my $node = extract_param
($param, 'node');
2682 my $vmid = extract_param
($param, 'vmid');
2684 my $snapname = extract_param
($param, 'snapname');
2687 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2688 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2691 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2694 __PACKAGE__-
>register_method({
2696 path
=> '{vmid}/template',
2700 description
=> "Create a Template.",
2702 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2703 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2706 additionalProperties
=> 0,
2708 node
=> get_standard_option
('pve-node'),
2709 vmid
=> get_standard_option
('pve-vmid'),
2713 description
=> "If you want to convert only 1 disk to base image.",
2714 enum
=> [PVE
::QemuServer
::disknames
()],
2719 returns
=> { type
=> 'null'},
2723 my $rpcenv = PVE
::RPCEnvironment
::get
();
2725 my $authuser = $rpcenv->get_user();
2727 my $node = extract_param
($param, 'node');
2729 my $vmid = extract_param
($param, 'vmid');
2731 my $disk = extract_param
($param, 'disk');
2733 my $updatefn = sub {
2735 my $conf = PVE
::QemuServer
::load_config
($vmid);
2737 PVE
::QemuServer
::check_lock
($conf);
2739 die "unable to create template, because VM contains snapshots\n"
2740 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2742 die "you can't convert a template to a template\n"
2743 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2745 die "you can't convert a VM to template if VM is running\n"
2746 if PVE
::QemuServer
::check_running
($vmid);
2749 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2752 $conf->{template
} = 1;
2753 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2755 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2758 PVE
::QemuServer
::lock_config
($vmid, $updatefn);