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;
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}. " .
258 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
259 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
260 user
=> 'all', # check inside
265 additionalProperties
=> 0,
266 properties
=> PVE
::QemuServer
::json_config_properties
(
268 node
=> get_standard_option
('pve-node'),
269 vmid
=> get_standard_option
('pve-vmid'),
271 description
=> "The backup file.",
276 storage
=> get_standard_option
('pve-storage-id', {
277 description
=> "Default storage.",
283 description
=> "Allow to overwrite existing VM.",
284 requires
=> 'archive',
289 description
=> "Assign a unique random ethernet address.",
290 requires
=> 'archive',
294 type
=> 'string', format
=> 'pve-poolid',
295 description
=> "Add the VM to the specified pool.",
305 my $rpcenv = PVE
::RPCEnvironment
::get
();
307 my $authuser = $rpcenv->get_user();
309 my $node = extract_param
($param, 'node');
311 my $vmid = extract_param
($param, 'vmid');
313 my $archive = extract_param
($param, 'archive');
315 my $storage = extract_param
($param, 'storage');
317 my $force = extract_param
($param, 'force');
319 my $unique = extract_param
($param, 'unique');
321 my $pool = extract_param
($param, 'pool');
323 my $filename = PVE
::QemuServer
::config_file
($vmid);
325 my $storecfg = PVE
::Storage
::config
();
327 PVE
::Cluster
::check_cfs_quorum
();
329 if (defined($pool)) {
330 $rpcenv->check_pool_exist($pool);
333 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
334 if defined($storage);
336 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
338 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
340 } elsif ($archive && $force && (-f
$filename) &&
341 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
342 # OK: user has VM.Backup permissions, and want to restore an existing VM
348 &$resolve_cdrom_alias($param);
350 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
352 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
354 foreach my $opt (keys %$param) {
355 if (PVE
::QemuServer
::valid_drivename
($opt)) {
356 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
357 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
359 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
360 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
364 PVE
::QemuServer
::add_random_macs
($param);
366 my $keystr = join(' ', keys %$param);
367 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
369 if ($archive eq '-') {
370 die "pipe requires cli environment\n"
371 if $rpcenv->{type
} ne 'cli';
373 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
375 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
376 if PVE
::Storage
::parse_volume_id
($archive, 1);
378 die "can't find archive file '$archive'\n" if !($path && -f
$path);
383 my $addVMtoPoolFn = sub {
384 my $usercfg = cfs_read_file
("user.cfg");
385 if (my $data = $usercfg->{pools
}->{$pool}) {
386 $data->{vms
}->{$vmid} = 1;
387 $usercfg->{vms
}->{$vmid} = $pool;
388 cfs_write_file
("user.cfg", $usercfg);
392 my $restorefn = sub {
394 # fixme: this test does not work if VM exists on other node!
396 die "unable to restore vm $vmid: config file already exists\n"
399 die "unable to restore vm $vmid: vm is running\n"
400 if PVE
::QemuServer
::check_running
($vmid);
404 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
407 unique
=> $unique });
409 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
412 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
418 die "unable to create vm $vmid: config file already exists\n"
429 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
431 # try to be smart about bootdisk
432 my @disks = PVE
::QemuServer
::disknames
();
434 foreach my $ds (reverse @disks) {
435 next if !$conf->{$ds};
436 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
437 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
441 if (!$conf->{bootdisk
} && $firstdisk) {
442 $conf->{bootdisk
} = $firstdisk;
445 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
451 foreach my $volid (@$vollist) {
452 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
455 die "create failed - $err";
458 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
461 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
464 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
467 __PACKAGE__-
>register_method({
472 description
=> "Directory index",
477 additionalProperties
=> 0,
479 node
=> get_standard_option
('pve-node'),
480 vmid
=> get_standard_option
('pve-vmid'),
488 subdir
=> { type
=> 'string' },
491 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
497 { subdir
=> 'config' },
498 { subdir
=> 'status' },
499 { subdir
=> 'unlink' },
500 { subdir
=> 'vncproxy' },
501 { subdir
=> 'migrate' },
502 { subdir
=> 'resize' },
504 { subdir
=> 'rrddata' },
505 { subdir
=> 'monitor' },
506 { subdir
=> 'snapshot' },
512 __PACKAGE__-
>register_method({
514 path
=> '{vmid}/rrd',
516 protected
=> 1, # fixme: can we avoid that?
518 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
520 description
=> "Read VM RRD statistics (returns PNG)",
522 additionalProperties
=> 0,
524 node
=> get_standard_option
('pve-node'),
525 vmid
=> get_standard_option
('pve-vmid'),
527 description
=> "Specify the time frame you are interested in.",
529 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
532 description
=> "The list of datasources you want to display.",
533 type
=> 'string', format
=> 'pve-configid-list',
536 description
=> "The RRD consolidation function",
538 enum
=> [ 'AVERAGE', 'MAX' ],
546 filename
=> { type
=> 'string' },
552 return PVE
::Cluster
::create_rrd_graph
(
553 "pve2-vm/$param->{vmid}", $param->{timeframe
},
554 $param->{ds
}, $param->{cf
});
558 __PACKAGE__-
>register_method({
560 path
=> '{vmid}/rrddata',
562 protected
=> 1, # fixme: can we avoid that?
564 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
566 description
=> "Read VM RRD statistics",
568 additionalProperties
=> 0,
570 node
=> get_standard_option
('pve-node'),
571 vmid
=> get_standard_option
('pve-vmid'),
573 description
=> "Specify the time frame you are interested in.",
575 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
578 description
=> "The RRD consolidation function",
580 enum
=> [ 'AVERAGE', 'MAX' ],
595 return PVE
::Cluster
::create_rrd_data
(
596 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
600 __PACKAGE__-
>register_method({
602 path
=> '{vmid}/config',
605 description
=> "Get virtual machine configuration.",
607 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
610 additionalProperties
=> 0,
612 node
=> get_standard_option
('pve-node'),
613 vmid
=> get_standard_option
('pve-vmid'),
621 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
628 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
630 delete $conf->{snapshots
};
635 my $vm_is_volid_owner = sub {
636 my ($storecfg, $vmid, $volid) =@_;
638 if ($volid !~ m
|^/|) {
640 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
641 if ($owner && ($owner == $vmid)) {
649 my $test_deallocate_drive = sub {
650 my ($storecfg, $vmid, $key, $drive, $force) = @_;
652 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
653 my $volid = $drive->{file
};
654 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
655 if ($force || $key =~ m/^unused/) {
656 my $sid = PVE
::Storage
::parse_volume_id
($volid);
665 my $delete_drive = sub {
666 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
668 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
669 my $volid = $drive->{file
};
670 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
671 if ($force || $key =~ m/^unused/) {
672 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
675 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
680 delete $conf->{$key};
683 my $vmconfig_delete_option = sub {
684 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
686 return if !defined($conf->{$opt});
688 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
691 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
693 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
694 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
695 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
699 my $unplugwarning = "";
700 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
701 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
702 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
703 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
704 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
705 $unplugwarning = "<br>verify that your guest support acpi hotplug";
708 if($opt eq 'tablet'){
709 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
711 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
715 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
716 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
718 delete $conf->{$opt};
721 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
724 my $safe_num_ne = sub {
727 return 0 if !defined($a) && !defined($b);
728 return 1 if !defined($a);
729 return 1 if !defined($b);
734 my $vmconfig_update_disk = sub {
735 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
737 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
739 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
740 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
742 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
747 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
749 my $media = $drive->{media
} || 'disk';
750 my $oldmedia = $old_drive->{media
} || 'disk';
751 die "unable to change media type\n" if $media ne $oldmedia;
753 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
754 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
756 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
757 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
760 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
761 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
762 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
763 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
764 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
765 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
766 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
767 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
768 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
769 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
774 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
775 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
777 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
778 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
780 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
782 if (PVE
::QemuServer
::check_running
($vmid)) {
783 if ($drive->{file
} eq 'none') {
784 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
786 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
787 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
788 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
792 } else { # hotplug new disks
794 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
798 my $vmconfig_update_net = sub {
799 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
801 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
802 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
803 my $newnet = PVE
::QemuServer
::parse_net
($value);
805 if($oldnet->{model
} ne $newnet->{model
}){
806 #if model change, we try to hot-unplug
807 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
810 if($newnet->{bridge
} && $oldnet->{bridge
}){
811 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
813 if($newnet->{rate
} ne $oldnet->{rate
}){
814 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
817 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
818 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
819 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
823 #if bridge/nat mode change, we try to hot-unplug
824 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
829 $conf->{$opt} = $value;
830 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
831 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
833 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
835 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
838 my $vm_config_perm_list = [
848 __PACKAGE__-
>register_method({
850 path
=> '{vmid}/config',
854 description
=> "Set virtual machine options.",
856 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
859 additionalProperties
=> 0,
860 properties
=> PVE
::QemuServer
::json_config_properties
(
862 node
=> get_standard_option
('pve-node'),
863 vmid
=> get_standard_option
('pve-vmid'),
864 skiplock
=> get_standard_option
('skiplock'),
866 type
=> 'string', format
=> 'pve-configid-list',
867 description
=> "A list of settings you want to delete.",
872 description
=> $opt_force_description,
874 requires
=> 'delete',
878 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
884 returns
=> { type
=> 'null'},
888 my $rpcenv = PVE
::RPCEnvironment
::get
();
890 my $authuser = $rpcenv->get_user();
892 my $node = extract_param
($param, 'node');
894 my $vmid = extract_param
($param, 'vmid');
896 my $digest = extract_param
($param, 'digest');
898 my @paramarr = (); # used for log message
899 foreach my $key (keys %$param) {
900 push @paramarr, "-$key", $param->{$key};
903 my $skiplock = extract_param
($param, 'skiplock');
904 raise_param_exc
({ skiplock
=> "Only root may use this option." })
905 if $skiplock && $authuser ne 'root@pam';
907 my $delete_str = extract_param
($param, 'delete');
909 my $force = extract_param
($param, 'force');
911 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
913 my $storecfg = PVE
::Storage
::config
();
915 my $defaults = PVE
::QemuServer
::load_defaults
();
917 &$resolve_cdrom_alias($param);
919 # now try to verify all parameters
922 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
923 $opt = 'ide2' if $opt eq 'cdrom';
924 raise_param_exc
({ delete => "you can't use '-$opt' and " .
925 "-delete $opt' at the same time" })
926 if defined($param->{$opt});
928 if (!PVE
::QemuServer
::option_exists
($opt)) {
929 raise_param_exc
({ delete => "unknown option '$opt'" });
935 foreach my $opt (keys %$param) {
936 if (PVE
::QemuServer
::valid_drivename
($opt)) {
938 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
939 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
940 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
941 } elsif ($opt =~ m/^net(\d+)$/) {
943 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
944 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
948 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
950 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
952 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
956 my $conf = PVE
::QemuServer
::load_config
($vmid);
958 die "checksum missmatch (file change by other user?)\n"
959 if $digest && $digest ne $conf->{digest
};
961 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
963 if ($param->{memory
} || defined($param->{balloon
})) {
964 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
965 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
967 die "balloon value too large (must be smaller than assigned memory)\n"
968 if $balloon > $maxmem;
971 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
973 foreach my $opt (@delete) { # delete
974 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
975 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
978 my $running = PVE
::QemuServer
::check_running
($vmid);
980 foreach my $opt (keys %$param) { # add/change
982 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
984 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
986 if (PVE
::QemuServer
::valid_drivename
($opt)) {
988 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
989 $opt, $param->{$opt}, $force);
991 } elsif ($opt =~ m/^net(\d+)$/) { #nics
993 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
994 $opt, $param->{$opt});
998 if($opt eq 'tablet' && $param->{$opt} == 1){
999 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1000 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
1001 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1004 $conf->{$opt} = $param->{$opt};
1005 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1009 # allow manual ballooning if shares is set to zero
1010 if ($running && defined($param->{balloon
}) &&
1011 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1012 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1013 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1018 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1024 __PACKAGE__-
>register_method({
1025 name
=> 'destroy_vm',
1030 description
=> "Destroy the vm (also delete all used/owned volumes).",
1032 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1035 additionalProperties
=> 0,
1037 node
=> get_standard_option
('pve-node'),
1038 vmid
=> get_standard_option
('pve-vmid'),
1039 skiplock
=> get_standard_option
('skiplock'),
1048 my $rpcenv = PVE
::RPCEnvironment
::get
();
1050 my $authuser = $rpcenv->get_user();
1052 my $vmid = $param->{vmid
};
1054 my $skiplock = $param->{skiplock
};
1055 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1056 if $skiplock && $authuser ne 'root@pam';
1059 my $conf = PVE
::QemuServer
::load_config
($vmid);
1061 my $storecfg = PVE
::Storage
::config
();
1063 my $delVMfromPoolFn = sub {
1064 my $usercfg = cfs_read_file
("user.cfg");
1065 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1066 if (my $data = $usercfg->{pools
}->{$pool}) {
1067 delete $data->{vms
}->{$vmid};
1068 delete $usercfg->{vms
}->{$vmid};
1069 cfs_write_file
("user.cfg", $usercfg);
1077 syslog
('info', "destroy VM $vmid: $upid\n");
1079 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1081 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
1084 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1087 __PACKAGE__-
>register_method({
1089 path
=> '{vmid}/unlink',
1093 description
=> "Unlink/delete disk images.",
1095 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1098 additionalProperties
=> 0,
1100 node
=> get_standard_option
('pve-node'),
1101 vmid
=> get_standard_option
('pve-vmid'),
1103 type
=> 'string', format
=> 'pve-configid-list',
1104 description
=> "A list of disk IDs you want to delete.",
1108 description
=> $opt_force_description,
1113 returns
=> { type
=> 'null'},
1117 $param->{delete} = extract_param
($param, 'idlist');
1119 __PACKAGE__-
>update_vm($param);
1126 __PACKAGE__-
>register_method({
1128 path
=> '{vmid}/vncproxy',
1132 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1134 description
=> "Creates a TCP VNC proxy connections.",
1136 additionalProperties
=> 0,
1138 node
=> get_standard_option
('pve-node'),
1139 vmid
=> get_standard_option
('pve-vmid'),
1143 additionalProperties
=> 0,
1145 user
=> { type
=> 'string' },
1146 ticket
=> { type
=> 'string' },
1147 cert
=> { type
=> 'string' },
1148 port
=> { type
=> 'integer' },
1149 upid
=> { type
=> 'string' },
1155 my $rpcenv = PVE
::RPCEnvironment
::get
();
1157 my $authuser = $rpcenv->get_user();
1159 my $vmid = $param->{vmid
};
1160 my $node = $param->{node
};
1162 my $authpath = "/vms/$vmid";
1164 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1166 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1169 my $port = PVE
::Tools
::next_vnc_port
();
1173 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1174 $remip = PVE
::Cluster
::remote_node_ip
($node);
1177 # NOTE: kvm VNC traffic is already TLS encrypted
1178 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1185 syslog
('info', "starting vnc proxy $upid\n");
1187 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1189 my $qmstr = join(' ', @$qmcmd);
1191 # also redirect stderr (else we get RFB protocol errors)
1192 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1194 PVE
::Tools
::run_command
($cmd);
1199 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1201 PVE
::Tools
::wait_for_vnc_port
($port);
1212 __PACKAGE__-
>register_method({
1214 path
=> '{vmid}/status',
1217 description
=> "Directory index",
1222 additionalProperties
=> 0,
1224 node
=> get_standard_option
('pve-node'),
1225 vmid
=> get_standard_option
('pve-vmid'),
1233 subdir
=> { type
=> 'string' },
1236 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1242 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1245 { subdir
=> 'current' },
1246 { subdir
=> 'start' },
1247 { subdir
=> 'stop' },
1253 my $vm_is_ha_managed = sub {
1256 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1257 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1263 __PACKAGE__-
>register_method({
1264 name
=> 'vm_status',
1265 path
=> '{vmid}/status/current',
1268 protected
=> 1, # qemu pid files are only readable by root
1269 description
=> "Get virtual machine status.",
1271 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1274 additionalProperties
=> 0,
1276 node
=> get_standard_option
('pve-node'),
1277 vmid
=> get_standard_option
('pve-vmid'),
1280 returns
=> { type
=> 'object' },
1285 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1287 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1288 my $status = $vmstatus->{$param->{vmid
}};
1290 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1295 __PACKAGE__-
>register_method({
1297 path
=> '{vmid}/status/start',
1301 description
=> "Start virtual machine.",
1303 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1306 additionalProperties
=> 0,
1308 node
=> get_standard_option
('pve-node'),
1309 vmid
=> get_standard_option
('pve-vmid'),
1310 skiplock
=> get_standard_option
('skiplock'),
1311 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1312 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1322 my $rpcenv = PVE
::RPCEnvironment
::get
();
1324 my $authuser = $rpcenv->get_user();
1326 my $node = extract_param
($param, 'node');
1328 my $vmid = extract_param
($param, 'vmid');
1330 my $stateuri = extract_param
($param, 'stateuri');
1331 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1332 if $stateuri && $authuser ne 'root@pam';
1334 my $skiplock = extract_param
($param, 'skiplock');
1335 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1336 if $skiplock && $authuser ne 'root@pam';
1338 my $migratedfrom = extract_param
($param, 'migratedfrom');
1339 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1340 if $migratedfrom && $authuser ne 'root@pam';
1342 my $storecfg = PVE
::Storage
::config
();
1344 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1345 $rpcenv->{type
} ne 'ha') {
1350 my $service = "pvevm:$vmid";
1352 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1354 print "Executing HA start for VM $vmid\n";
1356 PVE
::Tools
::run_command
($cmd);
1361 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1368 syslog
('info', "start VM $vmid: $upid\n");
1370 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1375 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1379 __PACKAGE__-
>register_method({
1381 path
=> '{vmid}/status/stop',
1385 description
=> "Stop virtual machine.",
1387 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1390 additionalProperties
=> 0,
1392 node
=> get_standard_option
('pve-node'),
1393 vmid
=> get_standard_option
('pve-vmid'),
1394 skiplock
=> get_standard_option
('skiplock'),
1395 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1397 description
=> "Wait maximal timeout seconds.",
1403 description
=> "Do not decativate storage volumes.",
1416 my $rpcenv = PVE
::RPCEnvironment
::get
();
1418 my $authuser = $rpcenv->get_user();
1420 my $node = extract_param
($param, 'node');
1422 my $vmid = extract_param
($param, 'vmid');
1424 my $skiplock = extract_param
($param, 'skiplock');
1425 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1426 if $skiplock && $authuser ne 'root@pam';
1428 my $keepActive = extract_param
($param, 'keepActive');
1429 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1430 if $keepActive && $authuser ne 'root@pam';
1432 my $migratedfrom = extract_param
($param, 'migratedfrom');
1433 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1434 if $migratedfrom && $authuser ne 'root@pam';
1437 my $storecfg = PVE
::Storage
::config
();
1439 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1444 my $service = "pvevm:$vmid";
1446 my $cmd = ['clusvcadm', '-d', $service];
1448 print "Executing HA stop for VM $vmid\n";
1450 PVE
::Tools
::run_command
($cmd);
1455 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1461 syslog
('info', "stop VM $vmid: $upid\n");
1463 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1464 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1469 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1473 __PACKAGE__-
>register_method({
1475 path
=> '{vmid}/status/reset',
1479 description
=> "Reset virtual machine.",
1481 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1484 additionalProperties
=> 0,
1486 node
=> get_standard_option
('pve-node'),
1487 vmid
=> get_standard_option
('pve-vmid'),
1488 skiplock
=> get_standard_option
('skiplock'),
1497 my $rpcenv = PVE
::RPCEnvironment
::get
();
1499 my $authuser = $rpcenv->get_user();
1501 my $node = extract_param
($param, 'node');
1503 my $vmid = extract_param
($param, 'vmid');
1505 my $skiplock = extract_param
($param, 'skiplock');
1506 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1507 if $skiplock && $authuser ne 'root@pam';
1509 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1514 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1519 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1522 __PACKAGE__-
>register_method({
1523 name
=> 'vm_shutdown',
1524 path
=> '{vmid}/status/shutdown',
1528 description
=> "Shutdown virtual machine.",
1530 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1533 additionalProperties
=> 0,
1535 node
=> get_standard_option
('pve-node'),
1536 vmid
=> get_standard_option
('pve-vmid'),
1537 skiplock
=> get_standard_option
('skiplock'),
1539 description
=> "Wait maximal timeout seconds.",
1545 description
=> "Make sure the VM stops.",
1551 description
=> "Do not decativate storage volumes.",
1564 my $rpcenv = PVE
::RPCEnvironment
::get
();
1566 my $authuser = $rpcenv->get_user();
1568 my $node = extract_param
($param, 'node');
1570 my $vmid = extract_param
($param, 'vmid');
1572 my $skiplock = extract_param
($param, 'skiplock');
1573 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1574 if $skiplock && $authuser ne 'root@pam';
1576 my $keepActive = extract_param
($param, 'keepActive');
1577 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1578 if $keepActive && $authuser ne 'root@pam';
1580 my $storecfg = PVE
::Storage
::config
();
1585 syslog
('info', "shutdown VM $vmid: $upid\n");
1587 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1588 1, $param->{forceStop
}, $keepActive);
1593 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1596 __PACKAGE__-
>register_method({
1597 name
=> 'vm_suspend',
1598 path
=> '{vmid}/status/suspend',
1602 description
=> "Suspend virtual machine.",
1604 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1607 additionalProperties
=> 0,
1609 node
=> get_standard_option
('pve-node'),
1610 vmid
=> get_standard_option
('pve-vmid'),
1611 skiplock
=> get_standard_option
('skiplock'),
1620 my $rpcenv = PVE
::RPCEnvironment
::get
();
1622 my $authuser = $rpcenv->get_user();
1624 my $node = extract_param
($param, 'node');
1626 my $vmid = extract_param
($param, 'vmid');
1628 my $skiplock = extract_param
($param, 'skiplock');
1629 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1630 if $skiplock && $authuser ne 'root@pam';
1632 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1637 syslog
('info', "suspend VM $vmid: $upid\n");
1639 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1644 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1647 __PACKAGE__-
>register_method({
1648 name
=> 'vm_resume',
1649 path
=> '{vmid}/status/resume',
1653 description
=> "Resume virtual machine.",
1655 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1658 additionalProperties
=> 0,
1660 node
=> get_standard_option
('pve-node'),
1661 vmid
=> get_standard_option
('pve-vmid'),
1662 skiplock
=> get_standard_option
('skiplock'),
1671 my $rpcenv = PVE
::RPCEnvironment
::get
();
1673 my $authuser = $rpcenv->get_user();
1675 my $node = extract_param
($param, 'node');
1677 my $vmid = extract_param
($param, 'vmid');
1679 my $skiplock = extract_param
($param, 'skiplock');
1680 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1681 if $skiplock && $authuser ne 'root@pam';
1683 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1688 syslog
('info', "resume VM $vmid: $upid\n");
1690 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1695 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1698 __PACKAGE__-
>register_method({
1699 name
=> 'vm_sendkey',
1700 path
=> '{vmid}/sendkey',
1704 description
=> "Send key event to virtual machine.",
1706 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1709 additionalProperties
=> 0,
1711 node
=> get_standard_option
('pve-node'),
1712 vmid
=> get_standard_option
('pve-vmid'),
1713 skiplock
=> get_standard_option
('skiplock'),
1715 description
=> "The key (qemu monitor encoding).",
1720 returns
=> { type
=> 'null'},
1724 my $rpcenv = PVE
::RPCEnvironment
::get
();
1726 my $authuser = $rpcenv->get_user();
1728 my $node = extract_param
($param, 'node');
1730 my $vmid = extract_param
($param, 'vmid');
1732 my $skiplock = extract_param
($param, 'skiplock');
1733 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1734 if $skiplock && $authuser ne 'root@pam';
1736 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1741 __PACKAGE__-
>register_method({
1742 name
=> 'vm_feature',
1743 path
=> '{vmid}/feature',
1747 description
=> "Check if feature for virtual machine is available.",
1749 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1752 additionalProperties
=> 0,
1754 node
=> get_standard_option
('pve-node'),
1755 vmid
=> get_standard_option
('pve-vmid'),
1757 description
=> "Feature to check.",
1759 enum
=> [ 'snapshot', 'clone' ],
1761 snapname
=> get_standard_option
('pve-snapshot-name', {
1772 my $node = extract_param
($param, 'node');
1774 my $vmid = extract_param
($param, 'vmid');
1776 my $snapname = extract_param
($param, 'snapname');
1778 my $feature = extract_param
($param, 'feature');
1780 my $running = PVE
::QemuServer
::check_running
($vmid);
1782 my $conf = PVE
::QemuServer
::load_config
($vmid);
1785 my $snap = $conf->{snapshots
}->{$snapname};
1786 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1789 my $storecfg = PVE
::Storage
::config
();
1791 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1792 my $res = $hasfeature ?
1 : 0 ;
1796 __PACKAGE__-
>register_method({
1798 path
=> '{vmid}/clone',
1802 description
=> "Create a copy of virtual machine/template.",
1804 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1805 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1806 "'Datastore.AllocateSpace' on any used storage.",
1809 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1811 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1812 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1817 additionalProperties
=> 0,
1819 node
=> get_standard_option
('pve-node'),
1820 vmid
=> get_standard_option
('pve-vmid'),
1821 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1824 type
=> 'string', format
=> 'dns-name',
1825 description
=> "Set a name for the new VM.",
1830 description
=> "Description for the new VM.",
1834 type
=> 'string', format
=> 'pve-poolid',
1835 description
=> "Add the new VM to the specified pool.",
1837 snapname
=> get_standard_option
('pve-snapshot-name', {
1841 storage
=> get_standard_option
('pve-storage-id', {
1842 description
=> "Target storage for full clone.",
1847 description
=> "Target format for file storage.",
1851 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1856 description
=> "Create a full copy of all disk. This is always done when " .
1857 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1860 target
=> get_standard_option
('pve-node', {
1861 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1872 my $rpcenv = PVE
::RPCEnvironment
::get
();
1874 my $authuser = $rpcenv->get_user();
1876 my $node = extract_param
($param, 'node');
1878 my $vmid = extract_param
($param, 'vmid');
1880 my $newid = extract_param
($param, 'newid');
1882 # fixme: update pool after create
1883 my $pool = extract_param
($param, 'pool');
1885 if (defined($pool)) {
1886 $rpcenv->check_pool_exist($pool);
1889 my $snapname = extract_param
($param, 'snapname');
1891 my $storage = extract_param
($param, 'storage');
1893 my $format = extract_param
($param, 'format');
1895 my $target = extract_param
($param, 'target');
1897 my $localnode = PVE
::INotify
::nodename
();
1899 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1901 PVE
::Cluster
::check_node_exists
($target) if $target;
1903 my $storecfg = PVE
::Storage
::config
();
1905 PVE
::Cluster
::check_cfs_quorum
();
1907 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1909 die "Clone running VM $vmid not implemented\n" if $running; # fixme: implement this
1911 # exclusive lock if VM is running - else shared lock is enough;
1912 my $shared_lock = $running ?
0 : 1;
1916 # do all tests after lock
1917 # we also try to do all tests before we fork the worker
1919 my $conf = PVE
::QemuServer
::load_config
($vmid);
1921 PVE
::QemuServer
::check_lock
($conf);
1923 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1925 die "unexpected state change\n" if $verify_running != $running;
1927 die "snapshot '$snapname' does not exist\n"
1928 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1930 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1932 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1934 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1936 my $conffile = PVE
::QemuServer
::config_file
($newid);
1938 die "unable to create VM $newid: config file already exists\n"
1941 my $newconf = { lock => 'clone' };
1945 foreach my $opt (keys %$oldconf) {
1946 my $value = $oldconf->{$opt};
1948 # do not copy snapshot related info
1949 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1950 $opt eq 'vmstate' || $opt eq 'snapstate';
1952 # always change MAC! address
1953 if ($opt =~ m/^net(\d+)$/) {
1954 my $net = PVE
::QemuServer
::parse_net
($value);
1955 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1956 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1957 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1958 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1959 $newconf->{$opt} = $value; # simply copy configuration
1961 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1962 die "Full clone feature is not available"
1963 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
1966 $drives->{$opt} = $drive;
1967 push @$vollist, $drive->{file
};
1970 # copy everything else
1971 $newconf->{$opt} = $value;
1975 delete $newconf->{template
};
1977 if ($param->{name
}) {
1978 $newconf->{name
} = $param->{name
};
1980 $newconf->{name
} = "Copy-of-$oldconf->{name}";
1983 if ($param->{description
}) {
1984 $newconf->{description
} = $param->{description
};
1987 # create empty/temp config - this fails if VM already exists on other node
1988 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
1993 my $newvollist = [];
1996 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1998 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2000 foreach my $opt (keys %$drives) {
2001 my $drive = $drives->{$opt};
2004 if (!$drive->{full
}) {
2005 print "create linked clone of drive $opt ($drive->{file})\n";
2006 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $drive->{file
}, $newid);
2007 push @$newvollist, $newvolid;
2010 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
});
2011 $storeid = $storage if $storage;
2017 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
2018 $fmt = $drive->{format
} || $defformat;
2021 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $drive->{file
}, 3);
2023 print "create ful clone of drive $opt ($drive->{file})\n";
2024 $newvolid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
2025 push @$newvollist, $newvolid;
2027 PVE
::QemuServer
::qemu_img_convert
($drive->{file
}, $newvolid, $size, $snapname);
2030 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $newvolid, 3);
2031 my $disk = { file
=> $newvolid, size
=> $size };
2032 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $disk);
2034 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2037 delete $newconf->{lock};
2038 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2041 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2042 die "Failed to move config to node '$target' - rename failed: $!\n"
2043 if !rename($conffile, $newconffile);
2049 sleep 1; # some storage like rbd need to wait before release volume - really?
2051 foreach my $volid (@$newvollist) {
2052 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2055 die "clone failed: $err";
2061 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2064 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2065 # Aquire exclusive lock lock for $newid
2066 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2071 __PACKAGE__-
>register_method({
2072 name
=> 'migrate_vm',
2073 path
=> '{vmid}/migrate',
2077 description
=> "Migrate virtual machine. Creates a new migration task.",
2079 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2082 additionalProperties
=> 0,
2084 node
=> get_standard_option
('pve-node'),
2085 vmid
=> get_standard_option
('pve-vmid'),
2086 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2089 description
=> "Use online/live migration.",
2094 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2101 description
=> "the task ID.",
2106 my $rpcenv = PVE
::RPCEnvironment
::get
();
2108 my $authuser = $rpcenv->get_user();
2110 my $target = extract_param
($param, 'target');
2112 my $localnode = PVE
::INotify
::nodename
();
2113 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2115 PVE
::Cluster
::check_cfs_quorum
();
2117 PVE
::Cluster
::check_node_exists
($target);
2119 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2121 my $vmid = extract_param
($param, 'vmid');
2123 raise_param_exc
({ force
=> "Only root may use this option." })
2124 if $param->{force
} && $authuser ne 'root@pam';
2127 my $conf = PVE
::QemuServer
::load_config
($vmid);
2129 # try to detect errors early
2131 PVE
::QemuServer
::check_lock
($conf);
2133 if (PVE
::QemuServer
::check_running
($vmid)) {
2134 die "cant migrate running VM without --online\n"
2135 if !$param->{online
};
2138 my $storecfg = PVE
::Storage
::config
();
2139 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2141 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2146 my $service = "pvevm:$vmid";
2148 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2150 print "Executing HA migrate for VM $vmid to node $target\n";
2152 PVE
::Tools
::run_command
($cmd);
2157 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2164 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2167 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2172 __PACKAGE__-
>register_method({
2174 path
=> '{vmid}/monitor',
2178 description
=> "Execute Qemu monitor commands.",
2180 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2183 additionalProperties
=> 0,
2185 node
=> get_standard_option
('pve-node'),
2186 vmid
=> get_standard_option
('pve-vmid'),
2189 description
=> "The monitor command.",
2193 returns
=> { type
=> 'string'},
2197 my $vmid = $param->{vmid
};
2199 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2203 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2205 $res = "ERROR: $@" if $@;
2210 __PACKAGE__-
>register_method({
2211 name
=> 'resize_vm',
2212 path
=> '{vmid}/resize',
2216 description
=> "Extend volume size.",
2218 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2221 additionalProperties
=> 0,
2223 node
=> get_standard_option
('pve-node'),
2224 vmid
=> get_standard_option
('pve-vmid'),
2225 skiplock
=> get_standard_option
('skiplock'),
2228 description
=> "The disk you want to resize.",
2229 enum
=> [PVE
::QemuServer
::disknames
()],
2233 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2234 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.",
2238 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2244 returns
=> { type
=> 'null'},
2248 my $rpcenv = PVE
::RPCEnvironment
::get
();
2250 my $authuser = $rpcenv->get_user();
2252 my $node = extract_param
($param, 'node');
2254 my $vmid = extract_param
($param, 'vmid');
2256 my $digest = extract_param
($param, 'digest');
2258 my $disk = extract_param
($param, 'disk');
2260 my $sizestr = extract_param
($param, 'size');
2262 my $skiplock = extract_param
($param, 'skiplock');
2263 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2264 if $skiplock && $authuser ne 'root@pam';
2266 my $storecfg = PVE
::Storage
::config
();
2268 my $updatefn = sub {
2270 my $conf = PVE
::QemuServer
::load_config
($vmid);
2272 die "checksum missmatch (file change by other user?)\n"
2273 if $digest && $digest ne $conf->{digest
};
2274 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2276 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2278 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2280 my $volid = $drive->{file
};
2282 die "disk '$disk' has no associated volume\n" if !$volid;
2284 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2286 die "you can't online resize a virtio windows bootdisk\n"
2287 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2289 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2291 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2293 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2295 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2296 my ($ext, $newsize, $unit) = ($1, $2, $4);
2299 $newsize = $newsize * 1024;
2300 } elsif ($unit eq 'M') {
2301 $newsize = $newsize * 1024 * 1024;
2302 } elsif ($unit eq 'G') {
2303 $newsize = $newsize * 1024 * 1024 * 1024;
2304 } elsif ($unit eq 'T') {
2305 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2308 $newsize += $size if $ext;
2309 $newsize = int($newsize);
2311 die "unable to skrink disk size\n" if $newsize < $size;
2313 return if $size == $newsize;
2315 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2317 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2319 $drive->{size
} = $newsize;
2320 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2322 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2325 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2329 __PACKAGE__-
>register_method({
2330 name
=> 'snapshot_list',
2331 path
=> '{vmid}/snapshot',
2333 description
=> "List all snapshots.",
2335 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2338 protected
=> 1, # qemu pid files are only readable by root
2340 additionalProperties
=> 0,
2342 vmid
=> get_standard_option
('pve-vmid'),
2343 node
=> get_standard_option
('pve-node'),
2352 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2357 my $vmid = $param->{vmid
};
2359 my $conf = PVE
::QemuServer
::load_config
($vmid);
2360 my $snaphash = $conf->{snapshots
} || {};
2364 foreach my $name (keys %$snaphash) {
2365 my $d = $snaphash->{$name};
2368 snaptime
=> $d->{snaptime
} || 0,
2369 vmstate
=> $d->{vmstate
} ?
1 : 0,
2370 description
=> $d->{description
} || '',
2372 $item->{parent
} = $d->{parent
} if $d->{parent
};
2373 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2377 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2378 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2379 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2381 push @$res, $current;
2386 __PACKAGE__-
>register_method({
2388 path
=> '{vmid}/snapshot',
2392 description
=> "Snapshot a VM.",
2394 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2397 additionalProperties
=> 0,
2399 node
=> get_standard_option
('pve-node'),
2400 vmid
=> get_standard_option
('pve-vmid'),
2401 snapname
=> get_standard_option
('pve-snapshot-name'),
2405 description
=> "Save the vmstate",
2410 description
=> "Freeze the filesystem",
2415 description
=> "A textual description or comment.",
2421 description
=> "the task ID.",
2426 my $rpcenv = PVE
::RPCEnvironment
::get
();
2428 my $authuser = $rpcenv->get_user();
2430 my $node = extract_param
($param, 'node');
2432 my $vmid = extract_param
($param, 'vmid');
2434 my $snapname = extract_param
($param, 'snapname');
2436 die "unable to use snapshot name 'current' (reserved name)\n"
2437 if $snapname eq 'current';
2440 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2441 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2442 $param->{freezefs
}, $param->{description
});
2445 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2448 __PACKAGE__-
>register_method({
2449 name
=> 'snapshot_cmd_idx',
2450 path
=> '{vmid}/snapshot/{snapname}',
2457 additionalProperties
=> 0,
2459 vmid
=> get_standard_option
('pve-vmid'),
2460 node
=> get_standard_option
('pve-node'),
2461 snapname
=> get_standard_option
('pve-snapshot-name'),
2470 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2477 push @$res, { cmd
=> 'rollback' };
2478 push @$res, { cmd
=> 'config' };
2483 __PACKAGE__-
>register_method({
2484 name
=> 'update_snapshot_config',
2485 path
=> '{vmid}/snapshot/{snapname}/config',
2489 description
=> "Update snapshot metadata.",
2491 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2494 additionalProperties
=> 0,
2496 node
=> get_standard_option
('pve-node'),
2497 vmid
=> get_standard_option
('pve-vmid'),
2498 snapname
=> get_standard_option
('pve-snapshot-name'),
2502 description
=> "A textual description or comment.",
2506 returns
=> { type
=> 'null' },
2510 my $rpcenv = PVE
::RPCEnvironment
::get
();
2512 my $authuser = $rpcenv->get_user();
2514 my $vmid = extract_param
($param, 'vmid');
2516 my $snapname = extract_param
($param, 'snapname');
2518 return undef if !defined($param->{description
});
2520 my $updatefn = sub {
2522 my $conf = PVE
::QemuServer
::load_config
($vmid);
2524 PVE
::QemuServer
::check_lock
($conf);
2526 my $snap = $conf->{snapshots
}->{$snapname};
2528 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2530 $snap->{description
} = $param->{description
} if defined($param->{description
});
2532 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2535 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2540 __PACKAGE__-
>register_method({
2541 name
=> 'get_snapshot_config',
2542 path
=> '{vmid}/snapshot/{snapname}/config',
2545 description
=> "Get snapshot configuration",
2547 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2550 additionalProperties
=> 0,
2552 node
=> get_standard_option
('pve-node'),
2553 vmid
=> get_standard_option
('pve-vmid'),
2554 snapname
=> get_standard_option
('pve-snapshot-name'),
2557 returns
=> { type
=> "object" },
2561 my $rpcenv = PVE
::RPCEnvironment
::get
();
2563 my $authuser = $rpcenv->get_user();
2565 my $vmid = extract_param
($param, 'vmid');
2567 my $snapname = extract_param
($param, 'snapname');
2569 my $conf = PVE
::QemuServer
::load_config
($vmid);
2571 my $snap = $conf->{snapshots
}->{$snapname};
2573 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2578 __PACKAGE__-
>register_method({
2580 path
=> '{vmid}/snapshot/{snapname}/rollback',
2584 description
=> "Rollback VM state to specified snapshot.",
2586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2589 additionalProperties
=> 0,
2591 node
=> get_standard_option
('pve-node'),
2592 vmid
=> get_standard_option
('pve-vmid'),
2593 snapname
=> get_standard_option
('pve-snapshot-name'),
2598 description
=> "the task ID.",
2603 my $rpcenv = PVE
::RPCEnvironment
::get
();
2605 my $authuser = $rpcenv->get_user();
2607 my $node = extract_param
($param, 'node');
2609 my $vmid = extract_param
($param, 'vmid');
2611 my $snapname = extract_param
($param, 'snapname');
2614 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2615 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2618 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2621 __PACKAGE__-
>register_method({
2622 name
=> 'delsnapshot',
2623 path
=> '{vmid}/snapshot/{snapname}',
2627 description
=> "Delete a VM snapshot.",
2629 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2632 additionalProperties
=> 0,
2634 node
=> get_standard_option
('pve-node'),
2635 vmid
=> get_standard_option
('pve-vmid'),
2636 snapname
=> get_standard_option
('pve-snapshot-name'),
2640 description
=> "For removal from config file, even if removing disk snapshots fails.",
2646 description
=> "the task ID.",
2651 my $rpcenv = PVE
::RPCEnvironment
::get
();
2653 my $authuser = $rpcenv->get_user();
2655 my $node = extract_param
($param, 'node');
2657 my $vmid = extract_param
($param, 'vmid');
2659 my $snapname = extract_param
($param, 'snapname');
2662 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2663 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2666 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2669 __PACKAGE__-
>register_method({
2671 path
=> '{vmid}/template',
2675 description
=> "Create a Template.",
2677 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
2679 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2680 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2684 additionalProperties
=> 0,
2686 node
=> get_standard_option
('pve-node'),
2687 vmid
=> get_standard_option
('pve-vmid'),
2691 description
=> "If you want to convert only 1 disk to base image.",
2692 enum
=> [PVE
::QemuServer
::disknames
()],
2697 returns
=> { type
=> 'null'},
2701 my $rpcenv = PVE
::RPCEnvironment
::get
();
2703 my $authuser = $rpcenv->get_user();
2705 my $node = extract_param
($param, 'node');
2707 my $vmid = extract_param
($param, 'vmid');
2709 my $disk = extract_param
($param, 'disk');
2711 my $updatefn = sub {
2713 my $conf = PVE
::QemuServer
::load_config
($vmid);
2715 PVE
::QemuServer
::check_lock
($conf);
2717 die "unable to create template, because VM contains snapshots\n"
2718 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2720 die "you can't convert a template to a template\n"
2721 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2723 die "you can't convert a VM to template if VM is running\n"
2724 if PVE
::QemuServer
::check_running
($vmid);
2727 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2730 $conf->{template
} = 1;
2731 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2733 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2736 PVE
::QemuServer
::lock_config
($vmid, $updatefn);