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/) {
666 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
669 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
674 delete $conf->{$key};
677 my $vmconfig_delete_option = sub {
678 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
680 return if !defined($conf->{$opt});
682 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
685 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
687 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
688 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
689 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
693 my $unplugwarning = "";
694 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
695 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
696 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
697 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
698 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
699 $unplugwarning = "<br>verify that your guest support acpi hotplug";
702 if($opt eq 'tablet'){
703 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
705 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
709 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
710 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
712 delete $conf->{$opt};
715 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
718 my $safe_num_ne = sub {
721 return 0 if !defined($a) && !defined($b);
722 return 1 if !defined($a);
723 return 1 if !defined($b);
728 my $vmconfig_update_disk = sub {
729 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
731 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
733 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
734 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
736 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
741 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
743 my $media = $drive->{media
} || 'disk';
744 my $oldmedia = $old_drive->{media
} || 'disk';
745 die "unable to change media type\n" if $media ne $oldmedia;
747 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
748 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
750 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
751 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
754 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
755 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
756 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
757 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
758 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
759 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
760 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
761 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
762 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
763 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
768 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
769 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
771 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
772 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
774 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
776 if (PVE
::QemuServer
::check_running
($vmid)) {
777 if ($drive->{file
} eq 'none') {
778 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
780 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
781 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
782 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
786 } else { # hotplug new disks
788 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
792 my $vmconfig_update_net = sub {
793 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
795 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
796 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
797 my $newnet = PVE
::QemuServer
::parse_net
($value);
799 if($oldnet->{model
} ne $newnet->{model
}){
800 #if model change, we try to hot-unplug
801 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
804 if($newnet->{bridge
} && $oldnet->{bridge
}){
805 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
807 if($newnet->{rate
} ne $oldnet->{rate
}){
808 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
811 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
812 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
813 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
817 #if bridge/nat mode change, we try to hot-unplug
818 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
823 $conf->{$opt} = $value;
824 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
825 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
827 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
829 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
832 my $vm_config_perm_list = [
842 __PACKAGE__-
>register_method({
844 path
=> '{vmid}/config',
848 description
=> "Set virtual machine options.",
850 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
853 additionalProperties
=> 0,
854 properties
=> PVE
::QemuServer
::json_config_properties
(
856 node
=> get_standard_option
('pve-node'),
857 vmid
=> get_standard_option
('pve-vmid'),
858 skiplock
=> get_standard_option
('skiplock'),
860 type
=> 'string', format
=> 'pve-configid-list',
861 description
=> "A list of settings you want to delete.",
866 description
=> $opt_force_description,
868 requires
=> 'delete',
872 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
878 returns
=> { type
=> 'null'},
882 my $rpcenv = PVE
::RPCEnvironment
::get
();
884 my $authuser = $rpcenv->get_user();
886 my $node = extract_param
($param, 'node');
888 my $vmid = extract_param
($param, 'vmid');
890 my $digest = extract_param
($param, 'digest');
892 my @paramarr = (); # used for log message
893 foreach my $key (keys %$param) {
894 push @paramarr, "-$key", $param->{$key};
897 my $skiplock = extract_param
($param, 'skiplock');
898 raise_param_exc
({ skiplock
=> "Only root may use this option." })
899 if $skiplock && $authuser ne 'root@pam';
901 my $delete_str = extract_param
($param, 'delete');
903 my $force = extract_param
($param, 'force');
905 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
907 my $storecfg = PVE
::Storage
::config
();
909 my $defaults = PVE
::QemuServer
::load_defaults
();
911 &$resolve_cdrom_alias($param);
913 # now try to verify all parameters
916 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
917 $opt = 'ide2' if $opt eq 'cdrom';
918 raise_param_exc
({ delete => "you can't use '-$opt' and " .
919 "-delete $opt' at the same time" })
920 if defined($param->{$opt});
922 if (!PVE
::QemuServer
::option_exists
($opt)) {
923 raise_param_exc
({ delete => "unknown option '$opt'" });
929 foreach my $opt (keys %$param) {
930 if (PVE
::QemuServer
::valid_drivename
($opt)) {
932 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
933 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
934 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
935 } elsif ($opt =~ m/^net(\d+)$/) {
937 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
938 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
942 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
944 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
946 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
950 my $conf = PVE
::QemuServer
::load_config
($vmid);
952 die "checksum missmatch (file change by other user?)\n"
953 if $digest && $digest ne $conf->{digest
};
955 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
957 if ($param->{memory
} || defined($param->{balloon
})) {
958 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
959 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
961 die "balloon value too large (must be smaller than assigned memory)\n"
962 if $balloon && $balloon > $maxmem;
965 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
967 foreach my $opt (@delete) { # delete
968 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
969 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
972 my $running = PVE
::QemuServer
::check_running
($vmid);
974 foreach my $opt (keys %$param) { # add/change
976 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
978 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
980 if (PVE
::QemuServer
::valid_drivename
($opt)) {
982 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
983 $opt, $param->{$opt}, $force);
985 } elsif ($opt =~ m/^net(\d+)$/) { #nics
987 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
988 $opt, $param->{$opt});
992 if($opt eq 'tablet' && $param->{$opt} == 1){
993 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
994 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
995 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
998 $conf->{$opt} = $param->{$opt};
999 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1003 # allow manual ballooning if shares is set to zero
1004 if ($running && defined($param->{balloon
}) &&
1005 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1006 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1007 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1012 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1018 __PACKAGE__-
>register_method({
1019 name
=> 'destroy_vm',
1024 description
=> "Destroy the vm (also delete all used/owned volumes).",
1026 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1029 additionalProperties
=> 0,
1031 node
=> get_standard_option
('pve-node'),
1032 vmid
=> get_standard_option
('pve-vmid'),
1033 skiplock
=> get_standard_option
('skiplock'),
1042 my $rpcenv = PVE
::RPCEnvironment
::get
();
1044 my $authuser = $rpcenv->get_user();
1046 my $vmid = $param->{vmid
};
1048 my $skiplock = $param->{skiplock
};
1049 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1050 if $skiplock && $authuser ne 'root@pam';
1053 my $conf = PVE
::QemuServer
::load_config
($vmid);
1055 my $storecfg = PVE
::Storage
::config
();
1057 my $delVMfromPoolFn = sub {
1058 my $usercfg = cfs_read_file
("user.cfg");
1059 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1060 if (my $data = $usercfg->{pools
}->{$pool}) {
1061 delete $data->{vms
}->{$vmid};
1062 delete $usercfg->{vms
}->{$vmid};
1063 cfs_write_file
("user.cfg", $usercfg);
1071 syslog
('info', "destroy VM $vmid: $upid\n");
1073 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1075 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1078 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1081 __PACKAGE__-
>register_method({
1083 path
=> '{vmid}/unlink',
1087 description
=> "Unlink/delete disk images.",
1089 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1092 additionalProperties
=> 0,
1094 node
=> get_standard_option
('pve-node'),
1095 vmid
=> get_standard_option
('pve-vmid'),
1097 type
=> 'string', format
=> 'pve-configid-list',
1098 description
=> "A list of disk IDs you want to delete.",
1102 description
=> $opt_force_description,
1107 returns
=> { type
=> 'null'},
1111 $param->{delete} = extract_param
($param, 'idlist');
1113 __PACKAGE__-
>update_vm($param);
1120 __PACKAGE__-
>register_method({
1122 path
=> '{vmid}/vncproxy',
1126 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1128 description
=> "Creates a TCP VNC proxy connections.",
1130 additionalProperties
=> 0,
1132 node
=> get_standard_option
('pve-node'),
1133 vmid
=> get_standard_option
('pve-vmid'),
1137 additionalProperties
=> 0,
1139 user
=> { type
=> 'string' },
1140 ticket
=> { type
=> 'string' },
1141 cert
=> { type
=> 'string' },
1142 port
=> { type
=> 'integer' },
1143 upid
=> { type
=> 'string' },
1149 my $rpcenv = PVE
::RPCEnvironment
::get
();
1151 my $authuser = $rpcenv->get_user();
1153 my $vmid = $param->{vmid
};
1154 my $node = $param->{node
};
1156 my $authpath = "/vms/$vmid";
1158 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1160 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1163 my $port = PVE
::Tools
::next_vnc_port
();
1167 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1168 $remip = PVE
::Cluster
::remote_node_ip
($node);
1171 # NOTE: kvm VNC traffic is already TLS encrypted
1172 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1179 syslog
('info', "starting vnc proxy $upid\n");
1181 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1183 my $qmstr = join(' ', @$qmcmd);
1185 # also redirect stderr (else we get RFB protocol errors)
1186 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1188 PVE
::Tools
::run_command
($cmd);
1193 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1195 PVE
::Tools
::wait_for_vnc_port
($port);
1206 __PACKAGE__-
>register_method({
1208 path
=> '{vmid}/status',
1211 description
=> "Directory index",
1216 additionalProperties
=> 0,
1218 node
=> get_standard_option
('pve-node'),
1219 vmid
=> get_standard_option
('pve-vmid'),
1227 subdir
=> { type
=> 'string' },
1230 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1236 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1239 { subdir
=> 'current' },
1240 { subdir
=> 'start' },
1241 { subdir
=> 'stop' },
1247 my $vm_is_ha_managed = sub {
1250 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1251 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1257 __PACKAGE__-
>register_method({
1258 name
=> 'vm_status',
1259 path
=> '{vmid}/status/current',
1262 protected
=> 1, # qemu pid files are only readable by root
1263 description
=> "Get virtual machine status.",
1265 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1268 additionalProperties
=> 0,
1270 node
=> get_standard_option
('pve-node'),
1271 vmid
=> get_standard_option
('pve-vmid'),
1274 returns
=> { type
=> 'object' },
1279 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1281 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1282 my $status = $vmstatus->{$param->{vmid
}};
1284 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1289 __PACKAGE__-
>register_method({
1291 path
=> '{vmid}/status/start',
1295 description
=> "Start virtual machine.",
1297 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1300 additionalProperties
=> 0,
1302 node
=> get_standard_option
('pve-node'),
1303 vmid
=> get_standard_option
('pve-vmid'),
1304 skiplock
=> get_standard_option
('skiplock'),
1305 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1306 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1316 my $rpcenv = PVE
::RPCEnvironment
::get
();
1318 my $authuser = $rpcenv->get_user();
1320 my $node = extract_param
($param, 'node');
1322 my $vmid = extract_param
($param, 'vmid');
1324 my $stateuri = extract_param
($param, 'stateuri');
1325 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1326 if $stateuri && $authuser ne 'root@pam';
1328 my $skiplock = extract_param
($param, 'skiplock');
1329 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1330 if $skiplock && $authuser ne 'root@pam';
1332 my $migratedfrom = extract_param
($param, 'migratedfrom');
1333 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1334 if $migratedfrom && $authuser ne 'root@pam';
1336 my $storecfg = PVE
::Storage
::config
();
1338 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1339 $rpcenv->{type
} ne 'ha') {
1344 my $service = "pvevm:$vmid";
1346 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1348 print "Executing HA start for VM $vmid\n";
1350 PVE
::Tools
::run_command
($cmd);
1355 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1362 syslog
('info', "start VM $vmid: $upid\n");
1364 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1369 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1373 __PACKAGE__-
>register_method({
1375 path
=> '{vmid}/status/stop',
1379 description
=> "Stop virtual machine.",
1381 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1384 additionalProperties
=> 0,
1386 node
=> get_standard_option
('pve-node'),
1387 vmid
=> get_standard_option
('pve-vmid'),
1388 skiplock
=> get_standard_option
('skiplock'),
1389 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1391 description
=> "Wait maximal timeout seconds.",
1397 description
=> "Do not decativate storage volumes.",
1410 my $rpcenv = PVE
::RPCEnvironment
::get
();
1412 my $authuser = $rpcenv->get_user();
1414 my $node = extract_param
($param, 'node');
1416 my $vmid = extract_param
($param, 'vmid');
1418 my $skiplock = extract_param
($param, 'skiplock');
1419 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1420 if $skiplock && $authuser ne 'root@pam';
1422 my $keepActive = extract_param
($param, 'keepActive');
1423 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1424 if $keepActive && $authuser ne 'root@pam';
1426 my $migratedfrom = extract_param
($param, 'migratedfrom');
1427 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1428 if $migratedfrom && $authuser ne 'root@pam';
1431 my $storecfg = PVE
::Storage
::config
();
1433 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1438 my $service = "pvevm:$vmid";
1440 my $cmd = ['clusvcadm', '-d', $service];
1442 print "Executing HA stop for VM $vmid\n";
1444 PVE
::Tools
::run_command
($cmd);
1449 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1455 syslog
('info', "stop VM $vmid: $upid\n");
1457 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1458 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1463 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1467 __PACKAGE__-
>register_method({
1469 path
=> '{vmid}/status/reset',
1473 description
=> "Reset virtual machine.",
1475 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1478 additionalProperties
=> 0,
1480 node
=> get_standard_option
('pve-node'),
1481 vmid
=> get_standard_option
('pve-vmid'),
1482 skiplock
=> get_standard_option
('skiplock'),
1491 my $rpcenv = PVE
::RPCEnvironment
::get
();
1493 my $authuser = $rpcenv->get_user();
1495 my $node = extract_param
($param, 'node');
1497 my $vmid = extract_param
($param, 'vmid');
1499 my $skiplock = extract_param
($param, 'skiplock');
1500 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1501 if $skiplock && $authuser ne 'root@pam';
1503 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1508 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1513 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1516 __PACKAGE__-
>register_method({
1517 name
=> 'vm_shutdown',
1518 path
=> '{vmid}/status/shutdown',
1522 description
=> "Shutdown virtual machine.",
1524 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1527 additionalProperties
=> 0,
1529 node
=> get_standard_option
('pve-node'),
1530 vmid
=> get_standard_option
('pve-vmid'),
1531 skiplock
=> get_standard_option
('skiplock'),
1533 description
=> "Wait maximal timeout seconds.",
1539 description
=> "Make sure the VM stops.",
1545 description
=> "Do not decativate storage volumes.",
1558 my $rpcenv = PVE
::RPCEnvironment
::get
();
1560 my $authuser = $rpcenv->get_user();
1562 my $node = extract_param
($param, 'node');
1564 my $vmid = extract_param
($param, 'vmid');
1566 my $skiplock = extract_param
($param, 'skiplock');
1567 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1568 if $skiplock && $authuser ne 'root@pam';
1570 my $keepActive = extract_param
($param, 'keepActive');
1571 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1572 if $keepActive && $authuser ne 'root@pam';
1574 my $storecfg = PVE
::Storage
::config
();
1579 syslog
('info', "shutdown VM $vmid: $upid\n");
1581 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1582 1, $param->{forceStop
}, $keepActive);
1587 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1590 __PACKAGE__-
>register_method({
1591 name
=> 'vm_suspend',
1592 path
=> '{vmid}/status/suspend',
1596 description
=> "Suspend virtual machine.",
1598 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1601 additionalProperties
=> 0,
1603 node
=> get_standard_option
('pve-node'),
1604 vmid
=> get_standard_option
('pve-vmid'),
1605 skiplock
=> get_standard_option
('skiplock'),
1614 my $rpcenv = PVE
::RPCEnvironment
::get
();
1616 my $authuser = $rpcenv->get_user();
1618 my $node = extract_param
($param, 'node');
1620 my $vmid = extract_param
($param, 'vmid');
1622 my $skiplock = extract_param
($param, 'skiplock');
1623 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1624 if $skiplock && $authuser ne 'root@pam';
1626 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1631 syslog
('info', "suspend VM $vmid: $upid\n");
1633 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1638 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1641 __PACKAGE__-
>register_method({
1642 name
=> 'vm_resume',
1643 path
=> '{vmid}/status/resume',
1647 description
=> "Resume virtual machine.",
1649 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1652 additionalProperties
=> 0,
1654 node
=> get_standard_option
('pve-node'),
1655 vmid
=> get_standard_option
('pve-vmid'),
1656 skiplock
=> get_standard_option
('skiplock'),
1665 my $rpcenv = PVE
::RPCEnvironment
::get
();
1667 my $authuser = $rpcenv->get_user();
1669 my $node = extract_param
($param, 'node');
1671 my $vmid = extract_param
($param, 'vmid');
1673 my $skiplock = extract_param
($param, 'skiplock');
1674 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1675 if $skiplock && $authuser ne 'root@pam';
1677 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1682 syslog
('info', "resume VM $vmid: $upid\n");
1684 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1689 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1692 __PACKAGE__-
>register_method({
1693 name
=> 'vm_sendkey',
1694 path
=> '{vmid}/sendkey',
1698 description
=> "Send key event to virtual machine.",
1700 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1703 additionalProperties
=> 0,
1705 node
=> get_standard_option
('pve-node'),
1706 vmid
=> get_standard_option
('pve-vmid'),
1707 skiplock
=> get_standard_option
('skiplock'),
1709 description
=> "The key (qemu monitor encoding).",
1714 returns
=> { type
=> 'null'},
1718 my $rpcenv = PVE
::RPCEnvironment
::get
();
1720 my $authuser = $rpcenv->get_user();
1722 my $node = extract_param
($param, 'node');
1724 my $vmid = extract_param
($param, 'vmid');
1726 my $skiplock = extract_param
($param, 'skiplock');
1727 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1728 if $skiplock && $authuser ne 'root@pam';
1730 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1735 __PACKAGE__-
>register_method({
1736 name
=> 'vm_feature',
1737 path
=> '{vmid}/feature',
1741 description
=> "Check if feature for virtual machine is available.",
1743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1746 additionalProperties
=> 0,
1748 node
=> get_standard_option
('pve-node'),
1749 vmid
=> get_standard_option
('pve-vmid'),
1751 description
=> "Feature to check.",
1753 enum
=> [ 'snapshot', 'clone', 'copy' ],
1755 snapname
=> get_standard_option
('pve-snapshot-name', {
1763 hasFeature
=> { type
=> 'boolean' },
1766 items
=> { type
=> 'string' },
1773 my $node = extract_param
($param, 'node');
1775 my $vmid = extract_param
($param, 'vmid');
1777 my $snapname = extract_param
($param, 'snapname');
1779 my $feature = extract_param
($param, 'feature');
1781 my $running = PVE
::QemuServer
::check_running
($vmid);
1783 my $conf = PVE
::QemuServer
::load_config
($vmid);
1786 my $snap = $conf->{snapshots
}->{$snapname};
1787 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1790 my $storecfg = PVE
::Storage
::config
();
1792 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1793 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1796 hasFeature
=> $hasFeature,
1797 nodes
=> [ keys %$nodelist ],
1801 __PACKAGE__-
>register_method({
1803 path
=> '{vmid}/clone',
1807 description
=> "Create a copy of virtual machine/template.",
1809 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1810 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1811 "'Datastore.AllocateSpace' on any used storage.",
1814 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1816 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1817 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1822 additionalProperties
=> 0,
1824 node
=> get_standard_option
('pve-node'),
1825 vmid
=> get_standard_option
('pve-vmid'),
1826 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1829 type
=> 'string', format
=> 'dns-name',
1830 description
=> "Set a name for the new VM.",
1835 description
=> "Description for the new VM.",
1839 type
=> 'string', format
=> 'pve-poolid',
1840 description
=> "Add the new VM to the specified pool.",
1842 snapname
=> get_standard_option
('pve-snapshot-name', {
1846 storage
=> get_standard_option
('pve-storage-id', {
1847 description
=> "Target storage for full clone.",
1852 description
=> "Target format for file storage.",
1856 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1861 description
=> "Create a full copy of all disk. This is always done when " .
1862 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1865 target
=> get_standard_option
('pve-node', {
1866 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1877 my $rpcenv = PVE
::RPCEnvironment
::get
();
1879 my $authuser = $rpcenv->get_user();
1881 my $node = extract_param
($param, 'node');
1883 my $vmid = extract_param
($param, 'vmid');
1885 my $newid = extract_param
($param, 'newid');
1887 my $pool = extract_param
($param, 'pool');
1889 if (defined($pool)) {
1890 $rpcenv->check_pool_exist($pool);
1893 my $snapname = extract_param
($param, 'snapname');
1895 my $storage = extract_param
($param, 'storage');
1897 my $format = extract_param
($param, 'format');
1899 my $target = extract_param
($param, 'target');
1901 my $localnode = PVE
::INotify
::nodename
();
1903 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1905 PVE
::Cluster
::check_node_exists
($target) if $target;
1907 my $storecfg = PVE
::Storage
::config
();
1910 # check if storage is enabled on local node
1911 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1913 # check if storage is available on target node
1914 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1915 # clone only works if target storage is shared
1916 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1917 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1921 PVE
::Cluster
::check_cfs_quorum
();
1923 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1925 # exclusive lock if VM is running - else shared lock is enough;
1926 my $shared_lock = $running ?
0 : 1;
1930 # do all tests after lock
1931 # we also try to do all tests before we fork the worker
1933 my $conf = PVE
::QemuServer
::load_config
($vmid);
1935 PVE
::QemuServer
::check_lock
($conf);
1937 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1939 die "unexpected state change\n" if $verify_running != $running;
1941 die "snapshot '$snapname' does not exist\n"
1942 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1944 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1946 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1948 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1950 my $conffile = PVE
::QemuServer
::config_file
($newid);
1952 die "unable to create VM $newid: config file already exists\n"
1955 my $newconf = { lock => 'clone' };
1959 foreach my $opt (keys %$oldconf) {
1960 my $value = $oldconf->{$opt};
1962 # do not copy snapshot related info
1963 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1964 $opt eq 'vmstate' || $opt eq 'snapstate';
1966 # always change MAC! address
1967 if ($opt =~ m/^net(\d+)$/) {
1968 my $net = PVE
::QemuServer
::parse_net
($value);
1969 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1970 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1971 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1972 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1973 $newconf->{$opt} = $value; # simply copy configuration
1975 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1976 die "Full clone feature is not available"
1977 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
1980 $drives->{$opt} = $drive;
1981 push @$vollist, $drive->{file
};
1984 # copy everything else
1985 $newconf->{$opt} = $value;
1989 delete $newconf->{template
};
1991 if ($param->{name
}) {
1992 $newconf->{name
} = $param->{name
};
1994 $newconf->{name
} = "Copy-of-$oldconf->{name}";
1997 if ($param->{description
}) {
1998 $newconf->{description
} = $param->{description
};
2001 # create empty/temp config - this fails if VM already exists on other node
2002 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2007 my $newvollist = [];
2010 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2012 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2014 foreach my $opt (keys %$drives) {
2015 my $drive = $drives->{$opt};
2018 if (!$drive->{full
}) {
2019 print "create linked clone of drive $opt ($drive->{file})\n";
2020 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
2021 push @$newvollist, $newvolid;
2024 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
2025 $storeid = $storage if $storage;
2031 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
2032 $fmt = $drive->{format
} || $defformat;
2035 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
2037 print "create full clone of drive $opt ($drive->{file})\n";
2038 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
2039 push @$newvollist, $newvolid;
2041 if(!$running || $snapname){
2042 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
2044 PVE
::QemuServer
::qemu_drive_mirror
($vmid, $opt, $newvolid, $newid);
2049 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
2051 $disk->{full
} = undef;
2052 $disk->{format
} = undef;
2053 $disk->{file
} = $newvolid;
2054 $disk->{size
} = $size;
2056 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
2058 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2061 delete $newconf->{lock};
2062 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2065 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2066 die "Failed to move config to node '$target' - rename failed: $!\n"
2067 if !rename($conffile, $newconffile);
2070 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2075 sleep 1; # some storage like rbd need to wait before release volume - really?
2077 foreach my $volid (@$newvollist) {
2078 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2081 die "clone failed: $err";
2087 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2090 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2091 # Aquire exclusive lock lock for $newid
2092 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2097 __PACKAGE__-
>register_method({
2098 name
=> 'migrate_vm',
2099 path
=> '{vmid}/migrate',
2103 description
=> "Migrate virtual machine. Creates a new migration task.",
2105 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2108 additionalProperties
=> 0,
2110 node
=> get_standard_option
('pve-node'),
2111 vmid
=> get_standard_option
('pve-vmid'),
2112 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2115 description
=> "Use online/live migration.",
2120 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2127 description
=> "the task ID.",
2132 my $rpcenv = PVE
::RPCEnvironment
::get
();
2134 my $authuser = $rpcenv->get_user();
2136 my $target = extract_param
($param, 'target');
2138 my $localnode = PVE
::INotify
::nodename
();
2139 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2141 PVE
::Cluster
::check_cfs_quorum
();
2143 PVE
::Cluster
::check_node_exists
($target);
2145 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2147 my $vmid = extract_param
($param, 'vmid');
2149 raise_param_exc
({ force
=> "Only root may use this option." })
2150 if $param->{force
} && $authuser ne 'root@pam';
2153 my $conf = PVE
::QemuServer
::load_config
($vmid);
2155 # try to detect errors early
2157 PVE
::QemuServer
::check_lock
($conf);
2159 if (PVE
::QemuServer
::check_running
($vmid)) {
2160 die "cant migrate running VM without --online\n"
2161 if !$param->{online
};
2164 my $storecfg = PVE
::Storage
::config
();
2165 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2167 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2172 my $service = "pvevm:$vmid";
2174 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2176 print "Executing HA migrate for VM $vmid to node $target\n";
2178 PVE
::Tools
::run_command
($cmd);
2183 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2190 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2193 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2198 __PACKAGE__-
>register_method({
2200 path
=> '{vmid}/monitor',
2204 description
=> "Execute Qemu monitor commands.",
2206 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2209 additionalProperties
=> 0,
2211 node
=> get_standard_option
('pve-node'),
2212 vmid
=> get_standard_option
('pve-vmid'),
2215 description
=> "The monitor command.",
2219 returns
=> { type
=> 'string'},
2223 my $vmid = $param->{vmid
};
2225 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2229 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2231 $res = "ERROR: $@" if $@;
2236 __PACKAGE__-
>register_method({
2237 name
=> 'resize_vm',
2238 path
=> '{vmid}/resize',
2242 description
=> "Extend volume size.",
2244 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2247 additionalProperties
=> 0,
2249 node
=> get_standard_option
('pve-node'),
2250 vmid
=> get_standard_option
('pve-vmid'),
2251 skiplock
=> get_standard_option
('skiplock'),
2254 description
=> "The disk you want to resize.",
2255 enum
=> [PVE
::QemuServer
::disknames
()],
2259 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2260 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.",
2264 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2270 returns
=> { type
=> 'null'},
2274 my $rpcenv = PVE
::RPCEnvironment
::get
();
2276 my $authuser = $rpcenv->get_user();
2278 my $node = extract_param
($param, 'node');
2280 my $vmid = extract_param
($param, 'vmid');
2282 my $digest = extract_param
($param, 'digest');
2284 my $disk = extract_param
($param, 'disk');
2286 my $sizestr = extract_param
($param, 'size');
2288 my $skiplock = extract_param
($param, 'skiplock');
2289 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2290 if $skiplock && $authuser ne 'root@pam';
2292 my $storecfg = PVE
::Storage
::config
();
2294 my $updatefn = sub {
2296 my $conf = PVE
::QemuServer
::load_config
($vmid);
2298 die "checksum missmatch (file change by other user?)\n"
2299 if $digest && $digest ne $conf->{digest
};
2300 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2302 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2304 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2306 my $volid = $drive->{file
};
2308 die "disk '$disk' has no associated volume\n" if !$volid;
2310 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2312 die "you can't online resize a virtio windows bootdisk\n"
2313 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2315 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2317 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2319 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2321 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2322 my ($ext, $newsize, $unit) = ($1, $2, $4);
2325 $newsize = $newsize * 1024;
2326 } elsif ($unit eq 'M') {
2327 $newsize = $newsize * 1024 * 1024;
2328 } elsif ($unit eq 'G') {
2329 $newsize = $newsize * 1024 * 1024 * 1024;
2330 } elsif ($unit eq 'T') {
2331 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2334 $newsize += $size if $ext;
2335 $newsize = int($newsize);
2337 die "unable to skrink disk size\n" if $newsize < $size;
2339 return if $size == $newsize;
2341 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2343 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2345 $drive->{size
} = $newsize;
2346 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2348 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2351 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2355 __PACKAGE__-
>register_method({
2356 name
=> 'snapshot_list',
2357 path
=> '{vmid}/snapshot',
2359 description
=> "List all snapshots.",
2361 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2364 protected
=> 1, # qemu pid files are only readable by root
2366 additionalProperties
=> 0,
2368 vmid
=> get_standard_option
('pve-vmid'),
2369 node
=> get_standard_option
('pve-node'),
2378 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2383 my $vmid = $param->{vmid
};
2385 my $conf = PVE
::QemuServer
::load_config
($vmid);
2386 my $snaphash = $conf->{snapshots
} || {};
2390 foreach my $name (keys %$snaphash) {
2391 my $d = $snaphash->{$name};
2394 snaptime
=> $d->{snaptime
} || 0,
2395 vmstate
=> $d->{vmstate
} ?
1 : 0,
2396 description
=> $d->{description
} || '',
2398 $item->{parent
} = $d->{parent
} if $d->{parent
};
2399 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2403 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2404 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2405 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2407 push @$res, $current;
2412 __PACKAGE__-
>register_method({
2414 path
=> '{vmid}/snapshot',
2418 description
=> "Snapshot a VM.",
2420 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2423 additionalProperties
=> 0,
2425 node
=> get_standard_option
('pve-node'),
2426 vmid
=> get_standard_option
('pve-vmid'),
2427 snapname
=> get_standard_option
('pve-snapshot-name'),
2431 description
=> "Save the vmstate",
2436 description
=> "Freeze the filesystem",
2441 description
=> "A textual description or comment.",
2447 description
=> "the task ID.",
2452 my $rpcenv = PVE
::RPCEnvironment
::get
();
2454 my $authuser = $rpcenv->get_user();
2456 my $node = extract_param
($param, 'node');
2458 my $vmid = extract_param
($param, 'vmid');
2460 my $snapname = extract_param
($param, 'snapname');
2462 die "unable to use snapshot name 'current' (reserved name)\n"
2463 if $snapname eq 'current';
2466 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2467 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2468 $param->{freezefs
}, $param->{description
});
2471 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2474 __PACKAGE__-
>register_method({
2475 name
=> 'snapshot_cmd_idx',
2476 path
=> '{vmid}/snapshot/{snapname}',
2483 additionalProperties
=> 0,
2485 vmid
=> get_standard_option
('pve-vmid'),
2486 node
=> get_standard_option
('pve-node'),
2487 snapname
=> get_standard_option
('pve-snapshot-name'),
2496 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2503 push @$res, { cmd
=> 'rollback' };
2504 push @$res, { cmd
=> 'config' };
2509 __PACKAGE__-
>register_method({
2510 name
=> 'update_snapshot_config',
2511 path
=> '{vmid}/snapshot/{snapname}/config',
2515 description
=> "Update snapshot metadata.",
2517 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2520 additionalProperties
=> 0,
2522 node
=> get_standard_option
('pve-node'),
2523 vmid
=> get_standard_option
('pve-vmid'),
2524 snapname
=> get_standard_option
('pve-snapshot-name'),
2528 description
=> "A textual description or comment.",
2532 returns
=> { type
=> 'null' },
2536 my $rpcenv = PVE
::RPCEnvironment
::get
();
2538 my $authuser = $rpcenv->get_user();
2540 my $vmid = extract_param
($param, 'vmid');
2542 my $snapname = extract_param
($param, 'snapname');
2544 return undef if !defined($param->{description
});
2546 my $updatefn = sub {
2548 my $conf = PVE
::QemuServer
::load_config
($vmid);
2550 PVE
::QemuServer
::check_lock
($conf);
2552 my $snap = $conf->{snapshots
}->{$snapname};
2554 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2556 $snap->{description
} = $param->{description
} if defined($param->{description
});
2558 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2561 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2566 __PACKAGE__-
>register_method({
2567 name
=> 'get_snapshot_config',
2568 path
=> '{vmid}/snapshot/{snapname}/config',
2571 description
=> "Get snapshot configuration",
2573 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2576 additionalProperties
=> 0,
2578 node
=> get_standard_option
('pve-node'),
2579 vmid
=> get_standard_option
('pve-vmid'),
2580 snapname
=> get_standard_option
('pve-snapshot-name'),
2583 returns
=> { type
=> "object" },
2587 my $rpcenv = PVE
::RPCEnvironment
::get
();
2589 my $authuser = $rpcenv->get_user();
2591 my $vmid = extract_param
($param, 'vmid');
2593 my $snapname = extract_param
($param, 'snapname');
2595 my $conf = PVE
::QemuServer
::load_config
($vmid);
2597 my $snap = $conf->{snapshots
}->{$snapname};
2599 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2604 __PACKAGE__-
>register_method({
2606 path
=> '{vmid}/snapshot/{snapname}/rollback',
2610 description
=> "Rollback VM state to specified snapshot.",
2612 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2615 additionalProperties
=> 0,
2617 node
=> get_standard_option
('pve-node'),
2618 vmid
=> get_standard_option
('pve-vmid'),
2619 snapname
=> get_standard_option
('pve-snapshot-name'),
2624 description
=> "the task ID.",
2629 my $rpcenv = PVE
::RPCEnvironment
::get
();
2631 my $authuser = $rpcenv->get_user();
2633 my $node = extract_param
($param, 'node');
2635 my $vmid = extract_param
($param, 'vmid');
2637 my $snapname = extract_param
($param, 'snapname');
2640 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2641 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2644 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2647 __PACKAGE__-
>register_method({
2648 name
=> 'delsnapshot',
2649 path
=> '{vmid}/snapshot/{snapname}',
2653 description
=> "Delete a VM snapshot.",
2655 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2658 additionalProperties
=> 0,
2660 node
=> get_standard_option
('pve-node'),
2661 vmid
=> get_standard_option
('pve-vmid'),
2662 snapname
=> get_standard_option
('pve-snapshot-name'),
2666 description
=> "For removal from config file, even if removing disk snapshots fails.",
2672 description
=> "the task ID.",
2677 my $rpcenv = PVE
::RPCEnvironment
::get
();
2679 my $authuser = $rpcenv->get_user();
2681 my $node = extract_param
($param, 'node');
2683 my $vmid = extract_param
($param, 'vmid');
2685 my $snapname = extract_param
($param, 'snapname');
2688 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2689 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2692 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2695 __PACKAGE__-
>register_method({
2697 path
=> '{vmid}/template',
2701 description
=> "Create a Template.",
2703 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2704 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2707 additionalProperties
=> 0,
2709 node
=> get_standard_option
('pve-node'),
2710 vmid
=> get_standard_option
('pve-vmid'),
2714 description
=> "If you want to convert only 1 disk to base image.",
2715 enum
=> [PVE
::QemuServer
::disknames
()],
2720 returns
=> { type
=> 'null'},
2724 my $rpcenv = PVE
::RPCEnvironment
::get
();
2726 my $authuser = $rpcenv->get_user();
2728 my $node = extract_param
($param, 'node');
2730 my $vmid = extract_param
($param, 'vmid');
2732 my $disk = extract_param
($param, 'disk');
2734 my $updatefn = sub {
2736 my $conf = PVE
::QemuServer
::load_config
($vmid);
2738 PVE
::QemuServer
::check_lock
($conf);
2740 die "unable to create template, because VM contains snapshots\n"
2741 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2743 die "you can't convert a template to a template\n"
2744 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2746 die "you can't convert a VM to template if VM is running\n"
2747 if PVE
::QemuServer
::check_running
($vmid);
2750 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2753 $conf->{template
} = 1;
2754 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2756 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2759 PVE
::QemuServer
::lock_config
($vmid, $updatefn);