1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
21 use Data
::Dumper
; # fixme: remove
23 use base
qw(PVE::RESTHandler);
25 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
27 my $resolve_cdrom_alias = sub {
30 if (my $value = $param->{cdrom
}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2
} = $value;
33 delete $param->{cdrom
};
38 my $check_storage_access = sub {
39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
41 PVE
::QemuServer
::foreach_drive
($settings, sub {
42 my ($ds, $drive) = @_;
44 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
46 my $volid = $drive->{file
};
48 if (!$volid || $volid eq 'none') {
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
62 my $check_storage_access_copy = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
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;
251 __PACKAGE__-
>register_method({
255 description
=> "Create or restore a virtual machine.",
257 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
259 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
260 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
266 additionalProperties
=> 0,
267 properties
=> PVE
::QemuServer
::json_config_properties
(
269 node
=> get_standard_option
('pve-node'),
270 vmid
=> get_standard_option
('pve-vmid'),
272 description
=> "The backup file.",
277 storage
=> get_standard_option
('pve-storage-id', {
278 description
=> "Default storage.",
284 description
=> "Allow to overwrite existing VM.",
285 requires
=> 'archive',
290 description
=> "Assign a unique random ethernet address.",
291 requires
=> 'archive',
295 type
=> 'string', format
=> 'pve-poolid',
296 description
=> "Add the VM to the specified pool.",
306 my $rpcenv = PVE
::RPCEnvironment
::get
();
308 my $authuser = $rpcenv->get_user();
310 my $node = extract_param
($param, 'node');
312 my $vmid = extract_param
($param, 'vmid');
314 my $archive = extract_param
($param, 'archive');
316 my $storage = extract_param
($param, 'storage');
318 my $force = extract_param
($param, 'force');
320 my $unique = extract_param
($param, 'unique');
322 my $pool = extract_param
($param, 'pool');
324 my $filename = PVE
::QemuServer
::config_file
($vmid);
326 my $storecfg = PVE
::Storage
::config
();
328 PVE
::Cluster
::check_cfs_quorum
();
330 if (defined($pool)) {
331 $rpcenv->check_pool_exist($pool);
334 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
335 if defined($storage);
338 &$resolve_cdrom_alias($param);
340 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
342 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
344 foreach my $opt (keys %$param) {
345 if (PVE
::QemuServer
::valid_drivename
($opt)) {
346 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
347 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
349 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
350 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
354 PVE
::QemuServer
::add_random_macs
($param);
356 my $keystr = join(' ', keys %$param);
357 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
359 if ($archive eq '-') {
360 die "pipe requires cli environment\n"
361 if $rpcenv->{type
} ne 'cli';
363 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
365 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
366 if PVE
::Storage
::parse_volume_id
($archive, 1);
368 die "can't find archive file '$archive'\n" if !($path && -f
$path);
373 my $addVMtoPoolFn = sub {
374 my $usercfg = cfs_read_file
("user.cfg");
375 if (my $data = $usercfg->{pools
}->{$pool}) {
376 $data->{vms
}->{$vmid} = 1;
377 $usercfg->{vms
}->{$vmid} = $pool;
378 cfs_write_file
("user.cfg", $usercfg);
382 my $restorefn = sub {
384 # fixme: this test does not work if VM exists on other node!
386 die "unable to restore vm $vmid: config file already exists\n"
389 die "unable to restore vm $vmid: vm is running\n"
390 if PVE
::QemuServer
::check_running
($vmid);
394 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
397 unique
=> $unique });
399 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
402 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
408 die "unable to create vm $vmid: config file already exists\n"
419 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
421 # try to be smart about bootdisk
422 my @disks = PVE
::QemuServer
::disknames
();
424 foreach my $ds (reverse @disks) {
425 next if !$conf->{$ds};
426 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
427 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
431 if (!$conf->{bootdisk
} && $firstdisk) {
432 $conf->{bootdisk
} = $firstdisk;
435 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
441 foreach my $volid (@$vollist) {
442 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
445 die "create failed - $err";
448 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
451 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
454 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
457 __PACKAGE__-
>register_method({
462 description
=> "Directory index",
467 additionalProperties
=> 0,
469 node
=> get_standard_option
('pve-node'),
470 vmid
=> get_standard_option
('pve-vmid'),
478 subdir
=> { type
=> 'string' },
481 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
487 { subdir
=> 'config' },
488 { subdir
=> 'status' },
489 { subdir
=> 'unlink' },
490 { subdir
=> 'vncproxy' },
491 { subdir
=> 'migrate' },
492 { subdir
=> 'resize' },
494 { subdir
=> 'rrddata' },
495 { subdir
=> 'monitor' },
496 { subdir
=> 'snapshot' },
502 __PACKAGE__-
>register_method({
504 path
=> '{vmid}/rrd',
506 protected
=> 1, # fixme: can we avoid that?
508 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
510 description
=> "Read VM RRD statistics (returns PNG)",
512 additionalProperties
=> 0,
514 node
=> get_standard_option
('pve-node'),
515 vmid
=> get_standard_option
('pve-vmid'),
517 description
=> "Specify the time frame you are interested in.",
519 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
522 description
=> "The list of datasources you want to display.",
523 type
=> 'string', format
=> 'pve-configid-list',
526 description
=> "The RRD consolidation function",
528 enum
=> [ 'AVERAGE', 'MAX' ],
536 filename
=> { type
=> 'string' },
542 return PVE
::Cluster
::create_rrd_graph
(
543 "pve2-vm/$param->{vmid}", $param->{timeframe
},
544 $param->{ds
}, $param->{cf
});
548 __PACKAGE__-
>register_method({
550 path
=> '{vmid}/rrddata',
552 protected
=> 1, # fixme: can we avoid that?
554 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
556 description
=> "Read VM RRD statistics",
558 additionalProperties
=> 0,
560 node
=> get_standard_option
('pve-node'),
561 vmid
=> get_standard_option
('pve-vmid'),
563 description
=> "Specify the time frame you are interested in.",
565 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
568 description
=> "The RRD consolidation function",
570 enum
=> [ 'AVERAGE', 'MAX' ],
585 return PVE
::Cluster
::create_rrd_data
(
586 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
590 __PACKAGE__-
>register_method({
592 path
=> '{vmid}/config',
595 description
=> "Get virtual machine configuration.",
597 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
600 additionalProperties
=> 0,
602 node
=> get_standard_option
('pve-node'),
603 vmid
=> get_standard_option
('pve-vmid'),
611 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
618 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
620 delete $conf->{snapshots
};
625 my $vm_is_volid_owner = sub {
626 my ($storecfg, $vmid, $volid) =@_;
628 if ($volid !~ m
|^/|) {
630 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
631 if ($owner && ($owner == $vmid)) {
639 my $test_deallocate_drive = sub {
640 my ($storecfg, $vmid, $key, $drive, $force) = @_;
642 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
643 my $volid = $drive->{file
};
644 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
645 if ($force || $key =~ m/^unused/) {
646 my $sid = PVE
::Storage
::parse_volume_id
($volid);
655 my $delete_drive = sub {
656 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
658 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
659 my $volid = $drive->{file
};
660 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
661 if ($force || $key =~ m/^unused/) {
662 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
665 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
670 delete $conf->{$key};
673 my $vmconfig_delete_option = sub {
674 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
676 return if !defined($conf->{$opt});
678 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
681 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
683 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
684 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
685 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
689 my $unplugwarning = "";
690 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
691 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
692 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
693 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
694 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
695 $unplugwarning = "<br>verify that your guest support acpi hotplug";
698 if($opt eq 'tablet'){
699 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
701 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
705 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
706 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
708 delete $conf->{$opt};
711 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
714 my $safe_num_ne = sub {
717 return 0 if !defined($a) && !defined($b);
718 return 1 if !defined($a);
719 return 1 if !defined($b);
724 my $vmconfig_update_disk = sub {
725 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
727 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
729 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
730 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
732 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
737 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
739 my $media = $drive->{media
} || 'disk';
740 my $oldmedia = $old_drive->{media
} || 'disk';
741 die "unable to change media type\n" if $media ne $oldmedia;
743 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
744 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
746 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
747 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
750 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
751 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
752 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
753 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
754 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
755 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
756 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
757 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
758 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
759 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
764 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
765 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
767 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
768 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
770 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
772 if (PVE
::QemuServer
::check_running
($vmid)) {
773 if ($drive->{file
} eq 'none') {
774 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
776 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
777 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
778 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
782 } else { # hotplug new disks
784 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
788 my $vmconfig_update_net = sub {
789 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
791 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
792 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
793 my $newnet = PVE
::QemuServer
::parse_net
($value);
795 if($oldnet->{model
} ne $newnet->{model
}){
796 #if model change, we try to hot-unplug
797 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
800 if($newnet->{bridge
} && $oldnet->{bridge
}){
801 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
803 if($newnet->{rate
} ne $oldnet->{rate
}){
804 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
807 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
808 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
809 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
813 #if bridge/nat mode change, we try to hot-unplug
814 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
819 $conf->{$opt} = $value;
820 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
821 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
823 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
825 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
828 my $vm_config_perm_list = [
838 __PACKAGE__-
>register_method({
840 path
=> '{vmid}/config',
844 description
=> "Set virtual machine options.",
846 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
849 additionalProperties
=> 0,
850 properties
=> PVE
::QemuServer
::json_config_properties
(
852 node
=> get_standard_option
('pve-node'),
853 vmid
=> get_standard_option
('pve-vmid'),
854 skiplock
=> get_standard_option
('skiplock'),
856 type
=> 'string', format
=> 'pve-configid-list',
857 description
=> "A list of settings you want to delete.",
862 description
=> $opt_force_description,
864 requires
=> 'delete',
868 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
874 returns
=> { type
=> 'null'},
878 my $rpcenv = PVE
::RPCEnvironment
::get
();
880 my $authuser = $rpcenv->get_user();
882 my $node = extract_param
($param, 'node');
884 my $vmid = extract_param
($param, 'vmid');
886 my $digest = extract_param
($param, 'digest');
888 my @paramarr = (); # used for log message
889 foreach my $key (keys %$param) {
890 push @paramarr, "-$key", $param->{$key};
893 my $skiplock = extract_param
($param, 'skiplock');
894 raise_param_exc
({ skiplock
=> "Only root may use this option." })
895 if $skiplock && $authuser ne 'root@pam';
897 my $delete_str = extract_param
($param, 'delete');
899 my $force = extract_param
($param, 'force');
901 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
903 my $storecfg = PVE
::Storage
::config
();
905 my $defaults = PVE
::QemuServer
::load_defaults
();
907 &$resolve_cdrom_alias($param);
909 # now try to verify all parameters
912 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
913 $opt = 'ide2' if $opt eq 'cdrom';
914 raise_param_exc
({ delete => "you can't use '-$opt' and " .
915 "-delete $opt' at the same time" })
916 if defined($param->{$opt});
918 if (!PVE
::QemuServer
::option_exists
($opt)) {
919 raise_param_exc
({ delete => "unknown option '$opt'" });
925 foreach my $opt (keys %$param) {
926 if (PVE
::QemuServer
::valid_drivename
($opt)) {
928 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
929 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
930 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
931 } elsif ($opt =~ m/^net(\d+)$/) {
933 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
934 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
938 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
940 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
942 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
946 my $conf = PVE
::QemuServer
::load_config
($vmid);
948 die "checksum missmatch (file change by other user?)\n"
949 if $digest && $digest ne $conf->{digest
};
951 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
953 if ($param->{memory
} || defined($param->{balloon
})) {
954 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
955 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
957 die "balloon value too large (must be smaller than assigned memory)\n"
958 if $balloon > $maxmem;
961 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
963 foreach my $opt (@delete) { # delete
964 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
965 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
968 my $running = PVE
::QemuServer
::check_running
($vmid);
970 foreach my $opt (keys %$param) { # add/change
972 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
974 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
976 if (PVE
::QemuServer
::valid_drivename
($opt)) {
978 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
979 $opt, $param->{$opt}, $force);
981 } elsif ($opt =~ m/^net(\d+)$/) { #nics
983 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
984 $opt, $param->{$opt});
988 if($opt eq 'tablet' && $param->{$opt} == 1){
989 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
990 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
991 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
994 $conf->{$opt} = $param->{$opt};
995 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
999 # allow manual ballooning if shares is set to zero
1000 if ($running && defined($param->{balloon
}) &&
1001 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1002 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1003 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1008 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1014 __PACKAGE__-
>register_method({
1015 name
=> 'destroy_vm',
1020 description
=> "Destroy the vm (also delete all used/owned volumes).",
1022 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1025 additionalProperties
=> 0,
1027 node
=> get_standard_option
('pve-node'),
1028 vmid
=> get_standard_option
('pve-vmid'),
1029 skiplock
=> get_standard_option
('skiplock'),
1038 my $rpcenv = PVE
::RPCEnvironment
::get
();
1040 my $authuser = $rpcenv->get_user();
1042 my $vmid = $param->{vmid
};
1044 my $skiplock = $param->{skiplock
};
1045 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1046 if $skiplock && $authuser ne 'root@pam';
1049 my $conf = PVE
::QemuServer
::load_config
($vmid);
1051 my $storecfg = PVE
::Storage
::config
();
1053 my $delVMfromPoolFn = sub {
1054 my $usercfg = cfs_read_file
("user.cfg");
1055 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1056 if (my $data = $usercfg->{pools
}->{$pool}) {
1057 delete $data->{vms
}->{$vmid};
1058 delete $usercfg->{vms
}->{$vmid};
1059 cfs_write_file
("user.cfg", $usercfg);
1067 syslog
('info', "destroy VM $vmid: $upid\n");
1069 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1071 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
1074 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1077 __PACKAGE__-
>register_method({
1079 path
=> '{vmid}/unlink',
1083 description
=> "Unlink/delete disk images.",
1085 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1088 additionalProperties
=> 0,
1090 node
=> get_standard_option
('pve-node'),
1091 vmid
=> get_standard_option
('pve-vmid'),
1093 type
=> 'string', format
=> 'pve-configid-list',
1094 description
=> "A list of disk IDs you want to delete.",
1098 description
=> $opt_force_description,
1103 returns
=> { type
=> 'null'},
1107 $param->{delete} = extract_param
($param, 'idlist');
1109 __PACKAGE__-
>update_vm($param);
1116 __PACKAGE__-
>register_method({
1118 path
=> '{vmid}/vncproxy',
1122 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1124 description
=> "Creates a TCP VNC proxy connections.",
1126 additionalProperties
=> 0,
1128 node
=> get_standard_option
('pve-node'),
1129 vmid
=> get_standard_option
('pve-vmid'),
1133 additionalProperties
=> 0,
1135 user
=> { type
=> 'string' },
1136 ticket
=> { type
=> 'string' },
1137 cert
=> { type
=> 'string' },
1138 port
=> { type
=> 'integer' },
1139 upid
=> { type
=> 'string' },
1145 my $rpcenv = PVE
::RPCEnvironment
::get
();
1147 my $authuser = $rpcenv->get_user();
1149 my $vmid = $param->{vmid
};
1150 my $node = $param->{node
};
1152 my $authpath = "/vms/$vmid";
1154 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1156 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1159 my $port = PVE
::Tools
::next_vnc_port
();
1163 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1164 $remip = PVE
::Cluster
::remote_node_ip
($node);
1167 # NOTE: kvm VNC traffic is already TLS encrypted
1168 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1175 syslog
('info', "starting vnc proxy $upid\n");
1177 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1179 my $qmstr = join(' ', @$qmcmd);
1181 # also redirect stderr (else we get RFB protocol errors)
1182 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1184 PVE
::Tools
::run_command
($cmd);
1189 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1191 PVE
::Tools
::wait_for_vnc_port
($port);
1202 __PACKAGE__-
>register_method({
1204 path
=> '{vmid}/status',
1207 description
=> "Directory index",
1212 additionalProperties
=> 0,
1214 node
=> get_standard_option
('pve-node'),
1215 vmid
=> get_standard_option
('pve-vmid'),
1223 subdir
=> { type
=> 'string' },
1226 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1232 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1235 { subdir
=> 'current' },
1236 { subdir
=> 'start' },
1237 { subdir
=> 'stop' },
1243 my $vm_is_ha_managed = sub {
1246 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1247 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1253 __PACKAGE__-
>register_method({
1254 name
=> 'vm_status',
1255 path
=> '{vmid}/status/current',
1258 protected
=> 1, # qemu pid files are only readable by root
1259 description
=> "Get virtual machine status.",
1261 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1264 additionalProperties
=> 0,
1266 node
=> get_standard_option
('pve-node'),
1267 vmid
=> get_standard_option
('pve-vmid'),
1270 returns
=> { type
=> 'object' },
1275 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1277 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1278 my $status = $vmstatus->{$param->{vmid
}};
1280 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1285 __PACKAGE__-
>register_method({
1287 path
=> '{vmid}/status/start',
1291 description
=> "Start virtual machine.",
1293 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1296 additionalProperties
=> 0,
1298 node
=> get_standard_option
('pve-node'),
1299 vmid
=> get_standard_option
('pve-vmid'),
1300 skiplock
=> get_standard_option
('skiplock'),
1301 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1302 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1312 my $rpcenv = PVE
::RPCEnvironment
::get
();
1314 my $authuser = $rpcenv->get_user();
1316 my $node = extract_param
($param, 'node');
1318 my $vmid = extract_param
($param, 'vmid');
1320 my $stateuri = extract_param
($param, 'stateuri');
1321 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1322 if $stateuri && $authuser ne 'root@pam';
1324 my $skiplock = extract_param
($param, 'skiplock');
1325 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1326 if $skiplock && $authuser ne 'root@pam';
1328 my $migratedfrom = extract_param
($param, 'migratedfrom');
1329 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1330 if $migratedfrom && $authuser ne 'root@pam';
1332 my $storecfg = PVE
::Storage
::config
();
1334 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1335 $rpcenv->{type
} ne 'ha') {
1340 my $service = "pvevm:$vmid";
1342 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1344 print "Executing HA start for VM $vmid\n";
1346 PVE
::Tools
::run_command
($cmd);
1351 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1358 syslog
('info', "start VM $vmid: $upid\n");
1360 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1365 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1369 __PACKAGE__-
>register_method({
1371 path
=> '{vmid}/status/stop',
1375 description
=> "Stop virtual machine.",
1377 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1380 additionalProperties
=> 0,
1382 node
=> get_standard_option
('pve-node'),
1383 vmid
=> get_standard_option
('pve-vmid'),
1384 skiplock
=> get_standard_option
('skiplock'),
1385 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1387 description
=> "Wait maximal timeout seconds.",
1393 description
=> "Do not decativate storage volumes.",
1406 my $rpcenv = PVE
::RPCEnvironment
::get
();
1408 my $authuser = $rpcenv->get_user();
1410 my $node = extract_param
($param, 'node');
1412 my $vmid = extract_param
($param, 'vmid');
1414 my $skiplock = extract_param
($param, 'skiplock');
1415 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1416 if $skiplock && $authuser ne 'root@pam';
1418 my $keepActive = extract_param
($param, 'keepActive');
1419 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1420 if $keepActive && $authuser ne 'root@pam';
1422 my $migratedfrom = extract_param
($param, 'migratedfrom');
1423 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1424 if $migratedfrom && $authuser ne 'root@pam';
1427 my $storecfg = PVE
::Storage
::config
();
1429 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1434 my $service = "pvevm:$vmid";
1436 my $cmd = ['clusvcadm', '-d', $service];
1438 print "Executing HA stop for VM $vmid\n";
1440 PVE
::Tools
::run_command
($cmd);
1445 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1451 syslog
('info', "stop VM $vmid: $upid\n");
1453 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1454 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1459 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1463 __PACKAGE__-
>register_method({
1465 path
=> '{vmid}/status/reset',
1469 description
=> "Reset virtual machine.",
1471 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1474 additionalProperties
=> 0,
1476 node
=> get_standard_option
('pve-node'),
1477 vmid
=> get_standard_option
('pve-vmid'),
1478 skiplock
=> get_standard_option
('skiplock'),
1487 my $rpcenv = PVE
::RPCEnvironment
::get
();
1489 my $authuser = $rpcenv->get_user();
1491 my $node = extract_param
($param, 'node');
1493 my $vmid = extract_param
($param, 'vmid');
1495 my $skiplock = extract_param
($param, 'skiplock');
1496 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1497 if $skiplock && $authuser ne 'root@pam';
1499 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1504 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1509 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1512 __PACKAGE__-
>register_method({
1513 name
=> 'vm_shutdown',
1514 path
=> '{vmid}/status/shutdown',
1518 description
=> "Shutdown virtual machine.",
1520 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1523 additionalProperties
=> 0,
1525 node
=> get_standard_option
('pve-node'),
1526 vmid
=> get_standard_option
('pve-vmid'),
1527 skiplock
=> get_standard_option
('skiplock'),
1529 description
=> "Wait maximal timeout seconds.",
1535 description
=> "Make sure the VM stops.",
1541 description
=> "Do not decativate storage volumes.",
1554 my $rpcenv = PVE
::RPCEnvironment
::get
();
1556 my $authuser = $rpcenv->get_user();
1558 my $node = extract_param
($param, 'node');
1560 my $vmid = extract_param
($param, 'vmid');
1562 my $skiplock = extract_param
($param, 'skiplock');
1563 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1564 if $skiplock && $authuser ne 'root@pam';
1566 my $keepActive = extract_param
($param, 'keepActive');
1567 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1568 if $keepActive && $authuser ne 'root@pam';
1570 my $storecfg = PVE
::Storage
::config
();
1575 syslog
('info', "shutdown VM $vmid: $upid\n");
1577 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1578 1, $param->{forceStop
}, $keepActive);
1583 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1586 __PACKAGE__-
>register_method({
1587 name
=> 'vm_suspend',
1588 path
=> '{vmid}/status/suspend',
1592 description
=> "Suspend virtual machine.",
1594 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1597 additionalProperties
=> 0,
1599 node
=> get_standard_option
('pve-node'),
1600 vmid
=> get_standard_option
('pve-vmid'),
1601 skiplock
=> get_standard_option
('skiplock'),
1610 my $rpcenv = PVE
::RPCEnvironment
::get
();
1612 my $authuser = $rpcenv->get_user();
1614 my $node = extract_param
($param, 'node');
1616 my $vmid = extract_param
($param, 'vmid');
1618 my $skiplock = extract_param
($param, 'skiplock');
1619 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1620 if $skiplock && $authuser ne 'root@pam';
1622 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1627 syslog
('info', "suspend VM $vmid: $upid\n");
1629 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1634 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1637 __PACKAGE__-
>register_method({
1638 name
=> 'vm_resume',
1639 path
=> '{vmid}/status/resume',
1643 description
=> "Resume virtual machine.",
1645 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1648 additionalProperties
=> 0,
1650 node
=> get_standard_option
('pve-node'),
1651 vmid
=> get_standard_option
('pve-vmid'),
1652 skiplock
=> get_standard_option
('skiplock'),
1661 my $rpcenv = PVE
::RPCEnvironment
::get
();
1663 my $authuser = $rpcenv->get_user();
1665 my $node = extract_param
($param, 'node');
1667 my $vmid = extract_param
($param, 'vmid');
1669 my $skiplock = extract_param
($param, 'skiplock');
1670 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1671 if $skiplock && $authuser ne 'root@pam';
1673 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1678 syslog
('info', "resume VM $vmid: $upid\n");
1680 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1685 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1688 __PACKAGE__-
>register_method({
1689 name
=> 'vm_sendkey',
1690 path
=> '{vmid}/sendkey',
1694 description
=> "Send key event to virtual machine.",
1696 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1699 additionalProperties
=> 0,
1701 node
=> get_standard_option
('pve-node'),
1702 vmid
=> get_standard_option
('pve-vmid'),
1703 skiplock
=> get_standard_option
('skiplock'),
1705 description
=> "The key (qemu monitor encoding).",
1710 returns
=> { type
=> 'null'},
1714 my $rpcenv = PVE
::RPCEnvironment
::get
();
1716 my $authuser = $rpcenv->get_user();
1718 my $node = extract_param
($param, 'node');
1720 my $vmid = extract_param
($param, 'vmid');
1722 my $skiplock = extract_param
($param, 'skiplock');
1723 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1724 if $skiplock && $authuser ne 'root@pam';
1726 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1731 __PACKAGE__-
>register_method({
1732 name
=> 'vm_feature',
1733 path
=> '{vmid}/feature',
1737 description
=> "Check if feature for virtual machine is available.",
1739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1742 additionalProperties
=> 0,
1744 node
=> get_standard_option
('pve-node'),
1745 vmid
=> get_standard_option
('pve-vmid'),
1747 description
=> "Feature to check.",
1749 enum
=> [ 'snapshot', 'clone' ],
1751 snapname
=> get_standard_option
('pve-snapshot-name', {
1762 my $node = extract_param
($param, 'node');
1764 my $vmid = extract_param
($param, 'vmid');
1766 my $snapname = extract_param
($param, 'snapname');
1768 my $feature = extract_param
($param, 'feature');
1770 my $running = PVE
::QemuServer
::check_running
($vmid);
1772 my $conf = PVE
::QemuServer
::load_config
($vmid);
1775 my $snap = $conf->{snapshots
}->{$snapname};
1776 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1779 my $storecfg = PVE
::Storage
::config
();
1781 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1782 my $res = $hasfeature ?
1 : 0 ;
1786 __PACKAGE__-
>register_method({
1788 path
=> '{vmid}/copy',
1792 description
=> "Create a copy of virtual machine/template.",
1794 description
=> "You need 'VM.Copy' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1795 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1796 "'Datastore.AllocateSpace' on any used storage.",
1799 ['perm', '/vms/{vmid}', [ 'VM.Copy' ]],
1801 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1802 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1807 additionalProperties
=> 0,
1809 node
=> get_standard_option
('pve-node'),
1810 vmid
=> get_standard_option
('pve-vmid'),
1811 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the copy.' }),
1814 type
=> 'string', format
=> 'dns-name',
1815 description
=> "Set a name for the new VM.",
1820 description
=> "Description for the new VM.",
1824 type
=> 'string', format
=> 'pve-poolid',
1825 description
=> "Add the new VM to the specified pool.",
1827 snapname
=> get_standard_option
('pve-snapshot-name', {
1831 storage
=> get_standard_option
('pve-storage-id', {
1832 description
=> "Target storage for full copy.",
1837 description
=> "Target format for file storage.",
1841 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1846 description
=> "Create a full copy of all disk. This is always done when " .
1847 "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
1850 target
=> get_standard_option
('pve-node', {
1851 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1862 my $rpcenv = PVE
::RPCEnvironment
::get
();
1864 my $authuser = $rpcenv->get_user();
1866 my $node = extract_param
($param, 'node');
1868 my $vmid = extract_param
($param, 'vmid');
1870 my $newid = extract_param
($param, 'newid');
1872 # fixme: update pool after create
1873 my $pool = extract_param
($param, 'pool');
1875 if (defined($pool)) {
1876 $rpcenv->check_pool_exist($pool);
1879 my $snapname = extract_param
($param, 'snapname');
1881 my $storage = extract_param
($param, 'storage');
1883 my $format = extract_param
($param, 'format');
1885 my $target = extract_param
($param, 'target');
1887 my $localnode = PVE
::INotify
::nodename
();
1889 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1891 PVE
::Cluster
::check_node_exists
($target) if $target;
1893 my $storecfg = PVE
::Storage
::config
();
1895 PVE
::Cluster
::check_cfs_quorum
();
1897 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1899 die "Copy running VM $vmid not implemented\n" if $running; # fixme: implement this
1901 # exclusive lock if VM is running - else shared lock is enough;
1902 my $shared_lock = $running ?
0 : 1;
1904 # fixme: do early checks - re-check after lock
1906 # fixme: impl. target node parameter (mv VM config if all storages are shared)
1910 # all tests after lock
1911 my $conf = PVE
::QemuServer
::load_config
($vmid);
1913 PVE
::QemuServer
::check_lock
($conf);
1915 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1917 die "unexpected state change\n" if $verify_running != $running;
1919 die "snapshot '$snapname' does not exist\n"
1920 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1922 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1924 my $sharedvm = &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1926 die "can't copy VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1928 my $conffile = PVE
::QemuServer
::config_file
($newid);
1930 die "unable to create VM $newid: config file already exists\n"
1933 # create empty/temp config - this fails if VM already exists on other node
1934 PVE
::Tools
::file_set_contents
($conffile, "# qmcopy temporary file\nlock: copy\n");
1939 my $newvollist = [];
1942 my $newconf = { lock => 'copy' };
1946 foreach my $opt (keys %$oldconf) {
1947 my $value = $oldconf->{$opt};
1949 # do not copy snapshot related info
1950 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1951 $opt eq 'vmstate' || $opt eq 'snapstate';
1953 # always change MAC! address
1954 if ($opt =~ m/^net(\d+)$/) {
1955 my $net = PVE
::QemuServer
::parse_net
($value);
1956 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1957 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1958 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1959 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1960 $newconf->{$opt} = $value; # simply copy configuration
1962 $drives->{$opt} = $drive;
1963 push @$vollist, $drive->{file
};
1966 # copy everything else
1967 $newconf->{$opt} = $value;
1971 delete $newconf->{template
};
1973 if ($param->{name
}) {
1974 $newconf->{name
} = $param->{name
};
1976 $newconf->{name
} = "Copy-of-$oldconf->{name}";
1979 if ($param->{description
}) {
1980 $newconf->{description
} = $param->{description
};
1983 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
1986 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1988 foreach my $opt (keys %$drives) {
1989 my $drive = $drives->{$opt};
1992 if (!$param->{full
} && PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1993 print "clone drive $opt ($drive->{file})\n";
1994 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
1995 push @$newvollist, $newvolid;
1998 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
1999 $storeid = $storage if $storage;
2005 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
2006 $fmt = $drive->{format
} || $defformat;
2009 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
2011 print "copy drive $opt ($drive->{file})\n";
2012 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
2013 push @$newvollist, $newvolid;
2015 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
2018 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
2019 my $disk = { file
=> $newvolid, size
=> $size };
2020 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
2022 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2027 delete $newconf->{lock};
2028 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2031 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2032 die "Failed to move config to node '$target' - rename failed: $!\n"
2033 if !rename($conffile, $newconffile);
2039 sleep 1; # some storage like rbd need to wait before release volume - really?
2041 foreach my $volid (@$newvollist) {
2042 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2045 die "copy failed: $err";
2051 return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
2054 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2055 # Aquire exclusive lock lock for $newid
2056 return PVE
::QemuServer
::lock_config_full
($newid, 1, $copyfn);
2061 __PACKAGE__-
>register_method({
2062 name
=> 'migrate_vm',
2063 path
=> '{vmid}/migrate',
2067 description
=> "Migrate virtual machine. Creates a new migration task.",
2069 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2072 additionalProperties
=> 0,
2074 node
=> get_standard_option
('pve-node'),
2075 vmid
=> get_standard_option
('pve-vmid'),
2076 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2079 description
=> "Use online/live migration.",
2084 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2091 description
=> "the task ID.",
2096 my $rpcenv = PVE
::RPCEnvironment
::get
();
2098 my $authuser = $rpcenv->get_user();
2100 my $target = extract_param
($param, 'target');
2102 my $localnode = PVE
::INotify
::nodename
();
2103 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2105 PVE
::Cluster
::check_cfs_quorum
();
2107 PVE
::Cluster
::check_node_exists
($target);
2109 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2111 my $vmid = extract_param
($param, 'vmid');
2113 raise_param_exc
({ force
=> "Only root may use this option." })
2114 if $param->{force
} && $authuser ne 'root@pam';
2117 my $conf = PVE
::QemuServer
::load_config
($vmid);
2119 # try to detect errors early
2121 PVE
::QemuServer
::check_lock
($conf);
2123 if (PVE
::QemuServer
::check_running
($vmid)) {
2124 die "cant migrate running VM without --online\n"
2125 if !$param->{online
};
2128 my $storecfg = PVE
::Storage
::config
();
2129 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2131 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2136 my $service = "pvevm:$vmid";
2138 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2140 print "Executing HA migrate for VM $vmid to node $target\n";
2142 PVE
::Tools
::run_command
($cmd);
2147 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2154 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2157 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2162 __PACKAGE__-
>register_method({
2164 path
=> '{vmid}/monitor',
2168 description
=> "Execute Qemu monitor commands.",
2170 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2173 additionalProperties
=> 0,
2175 node
=> get_standard_option
('pve-node'),
2176 vmid
=> get_standard_option
('pve-vmid'),
2179 description
=> "The monitor command.",
2183 returns
=> { type
=> 'string'},
2187 my $vmid = $param->{vmid
};
2189 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2193 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2195 $res = "ERROR: $@" if $@;
2200 __PACKAGE__-
>register_method({
2201 name
=> 'resize_vm',
2202 path
=> '{vmid}/resize',
2206 description
=> "Extend volume size.",
2208 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2211 additionalProperties
=> 0,
2213 node
=> get_standard_option
('pve-node'),
2214 vmid
=> get_standard_option
('pve-vmid'),
2215 skiplock
=> get_standard_option
('skiplock'),
2218 description
=> "The disk you want to resize.",
2219 enum
=> [PVE
::QemuServer
::disknames
()],
2223 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2224 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.",
2228 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2234 returns
=> { type
=> 'null'},
2238 my $rpcenv = PVE
::RPCEnvironment
::get
();
2240 my $authuser = $rpcenv->get_user();
2242 my $node = extract_param
($param, 'node');
2244 my $vmid = extract_param
($param, 'vmid');
2246 my $digest = extract_param
($param, 'digest');
2248 my $disk = extract_param
($param, 'disk');
2250 my $sizestr = extract_param
($param, 'size');
2252 my $skiplock = extract_param
($param, 'skiplock');
2253 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2254 if $skiplock && $authuser ne 'root@pam';
2256 my $storecfg = PVE
::Storage
::config
();
2258 my $updatefn = sub {
2260 my $conf = PVE
::QemuServer
::load_config
($vmid);
2262 die "checksum missmatch (file change by other user?)\n"
2263 if $digest && $digest ne $conf->{digest
};
2264 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2266 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2268 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2270 my $volid = $drive->{file
};
2272 die "disk '$disk' has no associated volume\n" if !$volid;
2274 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2276 die "you can't online resize a virtio windows bootdisk\n"
2277 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2279 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2281 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2283 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2285 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2286 my ($ext, $newsize, $unit) = ($1, $2, $4);
2289 $newsize = $newsize * 1024;
2290 } elsif ($unit eq 'M') {
2291 $newsize = $newsize * 1024 * 1024;
2292 } elsif ($unit eq 'G') {
2293 $newsize = $newsize * 1024 * 1024 * 1024;
2294 } elsif ($unit eq 'T') {
2295 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2298 $newsize += $size if $ext;
2299 $newsize = int($newsize);
2301 die "unable to skrink disk size\n" if $newsize < $size;
2303 return if $size == $newsize;
2305 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2307 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2309 $drive->{size
} = $newsize;
2310 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2312 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2315 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2319 __PACKAGE__-
>register_method({
2320 name
=> 'snapshot_list',
2321 path
=> '{vmid}/snapshot',
2323 description
=> "List all snapshots.",
2325 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2328 protected
=> 1, # qemu pid files are only readable by root
2330 additionalProperties
=> 0,
2332 vmid
=> get_standard_option
('pve-vmid'),
2333 node
=> get_standard_option
('pve-node'),
2342 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2347 my $vmid = $param->{vmid
};
2349 my $conf = PVE
::QemuServer
::load_config
($vmid);
2350 my $snaphash = $conf->{snapshots
} || {};
2354 foreach my $name (keys %$snaphash) {
2355 my $d = $snaphash->{$name};
2358 snaptime
=> $d->{snaptime
} || 0,
2359 vmstate
=> $d->{vmstate
} ?
1 : 0,
2360 description
=> $d->{description
} || '',
2362 $item->{parent
} = $d->{parent
} if $d->{parent
};
2363 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2367 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2368 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2369 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2371 push @$res, $current;
2376 __PACKAGE__-
>register_method({
2378 path
=> '{vmid}/snapshot',
2382 description
=> "Snapshot a VM.",
2384 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2387 additionalProperties
=> 0,
2389 node
=> get_standard_option
('pve-node'),
2390 vmid
=> get_standard_option
('pve-vmid'),
2391 snapname
=> get_standard_option
('pve-snapshot-name'),
2395 description
=> "Save the vmstate",
2400 description
=> "Freeze the filesystem",
2405 description
=> "A textual description or comment.",
2411 description
=> "the task ID.",
2416 my $rpcenv = PVE
::RPCEnvironment
::get
();
2418 my $authuser = $rpcenv->get_user();
2420 my $node = extract_param
($param, 'node');
2422 my $vmid = extract_param
($param, 'vmid');
2424 my $snapname = extract_param
($param, 'snapname');
2426 die "unable to use snapshot name 'current' (reserved name)\n"
2427 if $snapname eq 'current';
2430 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2431 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2432 $param->{freezefs
}, $param->{description
});
2435 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2438 __PACKAGE__-
>register_method({
2439 name
=> 'snapshot_cmd_idx',
2440 path
=> '{vmid}/snapshot/{snapname}',
2447 additionalProperties
=> 0,
2449 vmid
=> get_standard_option
('pve-vmid'),
2450 node
=> get_standard_option
('pve-node'),
2451 snapname
=> get_standard_option
('pve-snapshot-name'),
2460 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2467 push @$res, { cmd
=> 'rollback' };
2468 push @$res, { cmd
=> 'config' };
2473 __PACKAGE__-
>register_method({
2474 name
=> 'update_snapshot_config',
2475 path
=> '{vmid}/snapshot/{snapname}/config',
2479 description
=> "Update snapshot metadata.",
2481 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2484 additionalProperties
=> 0,
2486 node
=> get_standard_option
('pve-node'),
2487 vmid
=> get_standard_option
('pve-vmid'),
2488 snapname
=> get_standard_option
('pve-snapshot-name'),
2492 description
=> "A textual description or comment.",
2496 returns
=> { type
=> 'null' },
2500 my $rpcenv = PVE
::RPCEnvironment
::get
();
2502 my $authuser = $rpcenv->get_user();
2504 my $vmid = extract_param
($param, 'vmid');
2506 my $snapname = extract_param
($param, 'snapname');
2508 return undef if !defined($param->{description
});
2510 my $updatefn = sub {
2512 my $conf = PVE
::QemuServer
::load_config
($vmid);
2514 PVE
::QemuServer
::check_lock
($conf);
2516 my $snap = $conf->{snapshots
}->{$snapname};
2518 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2520 $snap->{description
} = $param->{description
} if defined($param->{description
});
2522 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2525 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2530 __PACKAGE__-
>register_method({
2531 name
=> 'get_snapshot_config',
2532 path
=> '{vmid}/snapshot/{snapname}/config',
2535 description
=> "Get snapshot configuration",
2537 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2540 additionalProperties
=> 0,
2542 node
=> get_standard_option
('pve-node'),
2543 vmid
=> get_standard_option
('pve-vmid'),
2544 snapname
=> get_standard_option
('pve-snapshot-name'),
2547 returns
=> { type
=> "object" },
2551 my $rpcenv = PVE
::RPCEnvironment
::get
();
2553 my $authuser = $rpcenv->get_user();
2555 my $vmid = extract_param
($param, 'vmid');
2557 my $snapname = extract_param
($param, 'snapname');
2559 my $conf = PVE
::QemuServer
::load_config
($vmid);
2561 my $snap = $conf->{snapshots
}->{$snapname};
2563 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2568 __PACKAGE__-
>register_method({
2570 path
=> '{vmid}/snapshot/{snapname}/rollback',
2574 description
=> "Rollback VM state to specified snapshot.",
2576 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2579 additionalProperties
=> 0,
2581 node
=> get_standard_option
('pve-node'),
2582 vmid
=> get_standard_option
('pve-vmid'),
2583 snapname
=> get_standard_option
('pve-snapshot-name'),
2588 description
=> "the task ID.",
2593 my $rpcenv = PVE
::RPCEnvironment
::get
();
2595 my $authuser = $rpcenv->get_user();
2597 my $node = extract_param
($param, 'node');
2599 my $vmid = extract_param
($param, 'vmid');
2601 my $snapname = extract_param
($param, 'snapname');
2604 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2605 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2608 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2611 __PACKAGE__-
>register_method({
2612 name
=> 'delsnapshot',
2613 path
=> '{vmid}/snapshot/{snapname}',
2617 description
=> "Delete a VM snapshot.",
2619 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2622 additionalProperties
=> 0,
2624 node
=> get_standard_option
('pve-node'),
2625 vmid
=> get_standard_option
('pve-vmid'),
2626 snapname
=> get_standard_option
('pve-snapshot-name'),
2630 description
=> "For removal from config file, even if removing disk snapshots fails.",
2636 description
=> "the task ID.",
2641 my $rpcenv = PVE
::RPCEnvironment
::get
();
2643 my $authuser = $rpcenv->get_user();
2645 my $node = extract_param
($param, 'node');
2647 my $vmid = extract_param
($param, 'vmid');
2649 my $snapname = extract_param
($param, 'snapname');
2652 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2653 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2656 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2659 __PACKAGE__-
>register_method({
2661 path
=> '{vmid}/template',
2665 description
=> "Create a Template.",
2667 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
2669 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2670 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2674 additionalProperties
=> 0,
2676 node
=> get_standard_option
('pve-node'),
2677 vmid
=> get_standard_option
('pve-vmid'),
2681 description
=> "If you want to convert only 1 disk to base image.",
2682 enum
=> [PVE
::QemuServer
::disknames
()],
2687 returns
=> { type
=> 'null'},
2691 my $rpcenv = PVE
::RPCEnvironment
::get
();
2693 my $authuser = $rpcenv->get_user();
2695 my $node = extract_param
($param, 'node');
2697 my $vmid = extract_param
($param, 'vmid');
2699 my $disk = extract_param
($param, 'disk');
2701 my $updatefn = sub {
2703 my $conf = PVE
::QemuServer
::load_config
($vmid);
2705 PVE
::QemuServer
::check_lock
($conf);
2707 die "unable to create template, because VM contains snapshots\n"
2708 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2710 die "you can't convert a template to a template\n"
2711 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2713 die "you can't convert a VM to template if VM is running\n"
2714 if PVE
::QemuServer
::check_running
($vmid);
2717 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2720 $conf->{template
} = 1;
2721 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2723 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2726 PVE
::QemuServer
::lock_config
($vmid, $updatefn);