1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
21 use Data
::Dumper
; # fixme: remove
23 use base
qw(PVE::RESTHandler);
25 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
27 my $resolve_cdrom_alias = sub {
30 if (my $value = $param->{cdrom
}) {
31 $value .= ",media=cdrom" if $value !~ m/media=/;
32 $param->{ide2
} = $value;
33 delete $param->{cdrom
};
38 my $check_storage_access = sub {
39 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
41 PVE
::QemuServer
::foreach_drive
($settings, sub {
42 my ($ds, $drive) = @_;
44 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
46 my $volid = $drive->{file
};
48 if (!$volid || $volid eq 'none') {
50 } elsif ($isCDROM && ($volid eq 'cdrom')) {
51 $rpcenv->check($authuser, "/", ['Sys.Console']);
52 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
53 my ($storeid, $size) = ($2 || $default_storage, $3);
54 die "no storage ID specified (and no default storage)\n" if !$storeid;
55 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
57 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
62 my $check_storage_access_clone = sub {
63 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
67 PVE
::QemuServer
::foreach_drive
($conf, sub {
68 my ($ds, $drive) = @_;
70 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
72 my $volid = $drive->{file
};
74 return if !$volid || $volid eq 'none';
77 if ($volid eq 'cdrom') {
78 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 # we simply allow access
81 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
82 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
83 $sharedvm = 0 if !$scfg->{shared
};
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
91 $sid = $storage if $storage;
92 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
99 # Note: $pool is only needed when creating a VM, because pool permissions
100 # are automatically inherited if VM already exists inside a pool.
101 my $create_disks = sub {
102 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
107 PVE
::QemuServer
::foreach_drive
($settings, sub {
108 my ($ds, $disk) = @_;
110 my $volid = $disk->{file
};
112 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
113 delete $disk->{size
};
114 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
115 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
116 my ($storeid, $size) = ($2 || $default_storage, $3);
117 die "no storage ID specified (and no default storage)\n" if !$storeid;
118 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
119 my $fmt = $disk->{format
} || $defformat;
120 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
121 $fmt, undef, $size*1024*1024);
122 $disk->{file
} = $volid;
123 $disk->{size
} = $size*1024*1024*1024;
124 push @$vollist, $volid;
125 delete $disk->{format
}; # no longer needed
126 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
129 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
131 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
133 my $foundvolid = undef;
136 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
137 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
139 PVE
::Storage
::foreach_volid
($dl, sub {
141 if($volumeid eq $volid) {
148 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
150 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
151 $disk->{size
} = $size;
152 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
156 # free allocated images on error
158 syslog
('err', "VM $vmid creating disks failed");
159 foreach my $volid (@$vollist) {
160 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
166 # modify vm config if everything went well
167 foreach my $ds (keys %$res) {
168 $conf->{$ds} = $res->{$ds};
174 my $check_vm_modify_config_perm = sub {
175 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
177 return 1 if $authuser eq 'root@pam';
179 foreach my $opt (@$key_list) {
180 # disk checks need to be done somewhere else
181 next if PVE
::QemuServer
::valid_drivename
($opt);
183 if ($opt eq 'sockets' || $opt eq 'cores' ||
184 $opt eq 'cpu' || $opt eq 'smp' ||
185 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
186 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
187 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
188 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
189 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
191 } elsif ($opt eq 'args' || $opt eq 'lock') {
192 die "only root can set '$opt' config\n";
193 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
194 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
195 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
196 } elsif ($opt =~ m/^net\d+$/) {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
206 __PACKAGE__-
>register_method({
210 description
=> "Virtual machine index (per node).",
212 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
216 protected
=> 1, # qemu pid files are only readable by root
218 additionalProperties
=> 0,
220 node
=> get_standard_option
('pve-node'),
229 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
234 my $rpcenv = PVE
::RPCEnvironment
::get
();
235 my $authuser = $rpcenv->get_user();
237 my $vmstatus = PVE
::QemuServer
::vmstatus
();
240 foreach my $vmid (keys %$vmstatus) {
241 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
243 my $data = $vmstatus->{$vmid};
244 $data->{vmid
} = $vmid;
253 __PACKAGE__-
>register_method({
257 description
=> "Create or restore a virtual machine.",
259 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
260 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
261 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
262 user
=> 'all', # check inside
267 additionalProperties
=> 0,
268 properties
=> PVE
::QemuServer
::json_config_properties
(
270 node
=> get_standard_option
('pve-node'),
271 vmid
=> get_standard_option
('pve-vmid'),
273 description
=> "The backup file.",
278 storage
=> get_standard_option
('pve-storage-id', {
279 description
=> "Default storage.",
285 description
=> "Allow to overwrite existing VM.",
286 requires
=> 'archive',
291 description
=> "Assign a unique random ethernet address.",
292 requires
=> 'archive',
296 type
=> 'string', format
=> 'pve-poolid',
297 description
=> "Add the VM to the specified pool.",
307 my $rpcenv = PVE
::RPCEnvironment
::get
();
309 my $authuser = $rpcenv->get_user();
311 my $node = extract_param
($param, 'node');
313 my $vmid = extract_param
($param, 'vmid');
315 my $archive = extract_param
($param, 'archive');
317 my $storage = extract_param
($param, 'storage');
319 my $force = extract_param
($param, 'force');
321 my $unique = extract_param
($param, 'unique');
323 my $pool = extract_param
($param, 'pool');
325 my $filename = PVE
::QemuServer
::config_file
($vmid);
327 my $storecfg = PVE
::Storage
::config
();
329 PVE
::Cluster
::check_cfs_quorum
();
331 if (defined($pool)) {
332 $rpcenv->check_pool_exist($pool);
335 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
336 if defined($storage);
338 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
340 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
342 } elsif ($archive && $force && (-f
$filename) &&
343 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
344 # OK: user has VM.Backup permissions, and want to restore an existing VM
350 &$resolve_cdrom_alias($param);
352 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
354 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
356 foreach my $opt (keys %$param) {
357 if (PVE
::QemuServer
::valid_drivename
($opt)) {
358 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
359 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
361 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
362 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
366 PVE
::QemuServer
::add_random_macs
($param);
368 my $keystr = join(' ', keys %$param);
369 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
371 if ($archive eq '-') {
372 die "pipe requires cli environment\n"
373 if $rpcenv->{type
} ne 'cli';
375 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
377 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
378 if PVE
::Storage
::parse_volume_id
($archive, 1);
380 die "can't find archive file '$archive'\n" if !($path && -f
$path);
385 my $restorefn = sub {
387 # fixme: this test does not work if VM exists on other node!
389 die "unable to restore vm $vmid: config file already exists\n"
392 die "unable to restore vm $vmid: vm is running\n"
393 if PVE
::QemuServer
::check_running
($vmid);
397 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
400 unique
=> $unique });
402 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
405 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
411 die "unable to create vm $vmid: config file already exists\n"
422 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
424 # try to be smart about bootdisk
425 my @disks = PVE
::QemuServer
::disknames
();
427 foreach my $ds (reverse @disks) {
428 next if !$conf->{$ds};
429 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
430 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
434 if (!$conf->{bootdisk
} && $firstdisk) {
435 $conf->{bootdisk
} = $firstdisk;
438 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
444 foreach my $volid (@$vollist) {
445 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
448 die "create failed - $err";
451 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
454 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
457 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
460 __PACKAGE__-
>register_method({
465 description
=> "Directory index",
470 additionalProperties
=> 0,
472 node
=> get_standard_option
('pve-node'),
473 vmid
=> get_standard_option
('pve-vmid'),
481 subdir
=> { type
=> 'string' },
484 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
490 { subdir
=> 'config' },
491 { subdir
=> 'status' },
492 { subdir
=> 'unlink' },
493 { subdir
=> 'vncproxy' },
494 { subdir
=> 'migrate' },
495 { subdir
=> 'resize' },
496 { subdir
=> 'move' },
498 { subdir
=> 'rrddata' },
499 { subdir
=> 'monitor' },
500 { subdir
=> 'snapshot' },
506 __PACKAGE__-
>register_method({
508 path
=> '{vmid}/rrd',
510 protected
=> 1, # fixme: can we avoid that?
512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
514 description
=> "Read VM RRD statistics (returns PNG)",
516 additionalProperties
=> 0,
518 node
=> get_standard_option
('pve-node'),
519 vmid
=> get_standard_option
('pve-vmid'),
521 description
=> "Specify the time frame you are interested in.",
523 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
526 description
=> "The list of datasources you want to display.",
527 type
=> 'string', format
=> 'pve-configid-list',
530 description
=> "The RRD consolidation function",
532 enum
=> [ 'AVERAGE', 'MAX' ],
540 filename
=> { type
=> 'string' },
546 return PVE
::Cluster
::create_rrd_graph
(
547 "pve2-vm/$param->{vmid}", $param->{timeframe
},
548 $param->{ds
}, $param->{cf
});
552 __PACKAGE__-
>register_method({
554 path
=> '{vmid}/rrddata',
556 protected
=> 1, # fixme: can we avoid that?
558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
560 description
=> "Read VM RRD statistics",
562 additionalProperties
=> 0,
564 node
=> get_standard_option
('pve-node'),
565 vmid
=> get_standard_option
('pve-vmid'),
567 description
=> "Specify the time frame you are interested in.",
569 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
572 description
=> "The RRD consolidation function",
574 enum
=> [ 'AVERAGE', 'MAX' ],
589 return PVE
::Cluster
::create_rrd_data
(
590 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
594 __PACKAGE__-
>register_method({
596 path
=> '{vmid}/config',
599 description
=> "Get virtual machine configuration.",
601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
604 additionalProperties
=> 0,
606 node
=> get_standard_option
('pve-node'),
607 vmid
=> get_standard_option
('pve-vmid'),
615 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
622 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
624 delete $conf->{snapshots
};
629 my $vm_is_volid_owner = sub {
630 my ($storecfg, $vmid, $volid) =@_;
632 if ($volid !~ m
|^/|) {
634 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
635 if ($owner && ($owner == $vmid)) {
643 my $test_deallocate_drive = sub {
644 my ($storecfg, $vmid, $key, $drive, $force) = @_;
646 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
647 my $volid = $drive->{file
};
648 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
649 if ($force || $key =~ m/^unused/) {
650 my $sid = PVE
::Storage
::parse_volume_id
($volid);
659 my $delete_drive = sub {
660 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
662 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
663 my $volid = $drive->{file
};
665 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
666 if ($force || $key =~ m/^unused/) {
668 # check if the disk is really unused
669 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, $key);
670 my $path = PVE
::Storage
::path
($storecfg, $volid);
672 die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
673 if $used_paths->{$path};
675 PVE
::Storage
::vdisk_free
($storecfg, $volid);
679 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
684 delete $conf->{$key};
687 my $vmconfig_delete_option = sub {
688 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
690 return if !defined($conf->{$opt});
692 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
695 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
697 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
698 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
699 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
703 my $unplugwarning = "";
704 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
705 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
706 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
707 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
708 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
709 $unplugwarning = "<br>verify that your guest support acpi hotplug";
712 if($opt eq 'tablet'){
713 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
715 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
719 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
720 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
722 delete $conf->{$opt};
725 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
728 my $safe_num_ne = sub {
731 return 0 if !defined($a) && !defined($b);
732 return 1 if !defined($a);
733 return 1 if !defined($b);
738 my $vmconfig_update_disk = sub {
739 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
741 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
743 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
744 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
746 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
751 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
753 my $media = $drive->{media
} || 'disk';
754 my $oldmedia = $old_drive->{media
} || 'disk';
755 die "unable to change media type\n" if $media ne $oldmedia;
757 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
758 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
760 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
761 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
764 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
765 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
766 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
767 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
768 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
769 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
770 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
771 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
772 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
773 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
778 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
779 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
781 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
782 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
784 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
786 if (PVE
::QemuServer
::check_running
($vmid)) {
787 if ($drive->{file
} eq 'none') {
788 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
790 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
791 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
792 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
796 } else { # hotplug new disks
798 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
802 my $vmconfig_update_net = sub {
803 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
805 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
806 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
807 my $newnet = PVE
::QemuServer
::parse_net
($value);
809 if($oldnet->{model
} ne $newnet->{model
}){
810 #if model change, we try to hot-unplug
811 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
814 if($newnet->{bridge
} && $oldnet->{bridge
}){
815 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
817 if($newnet->{rate
} ne $oldnet->{rate
}){
818 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
821 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
822 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
823 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
827 #if bridge/nat mode change, we try to hot-unplug
828 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
833 $conf->{$opt} = $value;
834 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
835 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
837 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
839 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
842 my $vm_config_perm_list = [
852 __PACKAGE__-
>register_method({
854 path
=> '{vmid}/config',
858 description
=> "Set virtual machine options.",
860 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
863 additionalProperties
=> 0,
864 properties
=> PVE
::QemuServer
::json_config_properties
(
866 node
=> get_standard_option
('pve-node'),
867 vmid
=> get_standard_option
('pve-vmid'),
868 skiplock
=> get_standard_option
('skiplock'),
870 type
=> 'string', format
=> 'pve-configid-list',
871 description
=> "A list of settings you want to delete.",
876 description
=> $opt_force_description,
878 requires
=> 'delete',
882 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
888 returns
=> { type
=> 'null'},
892 my $rpcenv = PVE
::RPCEnvironment
::get
();
894 my $authuser = $rpcenv->get_user();
896 my $node = extract_param
($param, 'node');
898 my $vmid = extract_param
($param, 'vmid');
900 my $digest = extract_param
($param, 'digest');
902 my @paramarr = (); # used for log message
903 foreach my $key (keys %$param) {
904 push @paramarr, "-$key", $param->{$key};
907 my $skiplock = extract_param
($param, 'skiplock');
908 raise_param_exc
({ skiplock
=> "Only root may use this option." })
909 if $skiplock && $authuser ne 'root@pam';
911 my $delete_str = extract_param
($param, 'delete');
913 my $force = extract_param
($param, 'force');
915 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
917 my $storecfg = PVE
::Storage
::config
();
919 my $defaults = PVE
::QemuServer
::load_defaults
();
921 &$resolve_cdrom_alias($param);
923 # now try to verify all parameters
926 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
927 $opt = 'ide2' if $opt eq 'cdrom';
928 raise_param_exc
({ delete => "you can't use '-$opt' and " .
929 "-delete $opt' at the same time" })
930 if defined($param->{$opt});
932 if (!PVE
::QemuServer
::option_exists
($opt)) {
933 raise_param_exc
({ delete => "unknown option '$opt'" });
939 foreach my $opt (keys %$param) {
940 if (PVE
::QemuServer
::valid_drivename
($opt)) {
942 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
943 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
944 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
945 } elsif ($opt =~ m/^net(\d+)$/) {
947 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
948 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
952 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
954 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
956 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
960 my $conf = PVE
::QemuServer
::load_config
($vmid);
962 die "checksum missmatch (file change by other user?)\n"
963 if $digest && $digest ne $conf->{digest
};
965 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
967 if ($param->{memory
} || defined($param->{balloon
})) {
968 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
969 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
971 die "balloon value too large (must be smaller than assigned memory)\n"
972 if $balloon && $balloon > $maxmem;
975 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
977 foreach my $opt (@delete) { # delete
978 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
979 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
982 my $running = PVE
::QemuServer
::check_running
($vmid);
984 foreach my $opt (keys %$param) { # add/change
986 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
988 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
990 if (PVE
::QemuServer
::valid_drivename
($opt)) {
992 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
993 $opt, $param->{$opt}, $force);
995 } elsif ($opt =~ m/^net(\d+)$/) { #nics
997 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
998 $opt, $param->{$opt});
1002 if($opt eq 'tablet' && $param->{$opt} == 1){
1003 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
1004 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
1005 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
1008 $conf->{$opt} = $param->{$opt};
1009 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1013 # allow manual ballooning if shares is set to zero
1014 if ($running && defined($param->{balloon
}) &&
1015 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
1016 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
1017 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
1022 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1028 __PACKAGE__-
>register_method({
1029 name
=> 'destroy_vm',
1034 description
=> "Destroy the vm (also delete all used/owned volumes).",
1036 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1039 additionalProperties
=> 0,
1041 node
=> get_standard_option
('pve-node'),
1042 vmid
=> get_standard_option
('pve-vmid'),
1043 skiplock
=> get_standard_option
('skiplock'),
1052 my $rpcenv = PVE
::RPCEnvironment
::get
();
1054 my $authuser = $rpcenv->get_user();
1056 my $vmid = $param->{vmid
};
1058 my $skiplock = $param->{skiplock
};
1059 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1060 if $skiplock && $authuser ne 'root@pam';
1063 my $conf = PVE
::QemuServer
::load_config
($vmid);
1065 my $storecfg = PVE
::Storage
::config
();
1067 my $delVMfromPoolFn = sub {
1068 my $usercfg = cfs_read_file
("user.cfg");
1069 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1070 if (my $data = $usercfg->{pools
}->{$pool}) {
1071 delete $data->{vms
}->{$vmid};
1072 delete $usercfg->{vms
}->{$vmid};
1073 cfs_write_file
("user.cfg", $usercfg);
1081 syslog
('info', "destroy VM $vmid: $upid\n");
1083 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1085 PVE
::AccessControl
::remove_vm_from_pool
($vmid);
1088 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1091 __PACKAGE__-
>register_method({
1093 path
=> '{vmid}/unlink',
1097 description
=> "Unlink/delete disk images.",
1099 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1102 additionalProperties
=> 0,
1104 node
=> get_standard_option
('pve-node'),
1105 vmid
=> get_standard_option
('pve-vmid'),
1107 type
=> 'string', format
=> 'pve-configid-list',
1108 description
=> "A list of disk IDs you want to delete.",
1112 description
=> $opt_force_description,
1117 returns
=> { type
=> 'null'},
1121 $param->{delete} = extract_param
($param, 'idlist');
1123 __PACKAGE__-
>update_vm($param);
1130 __PACKAGE__-
>register_method({
1132 path
=> '{vmid}/vncproxy',
1136 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1138 description
=> "Creates a TCP VNC proxy connections.",
1140 additionalProperties
=> 0,
1142 node
=> get_standard_option
('pve-node'),
1143 vmid
=> get_standard_option
('pve-vmid'),
1147 additionalProperties
=> 0,
1149 user
=> { type
=> 'string' },
1150 ticket
=> { type
=> 'string' },
1151 cert
=> { type
=> 'string' },
1152 port
=> { type
=> 'integer' },
1153 upid
=> { type
=> 'string' },
1159 my $rpcenv = PVE
::RPCEnvironment
::get
();
1161 my $authuser = $rpcenv->get_user();
1163 my $vmid = $param->{vmid
};
1164 my $node = $param->{node
};
1166 my $authpath = "/vms/$vmid";
1168 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1170 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1173 my $port = PVE
::Tools
::next_vnc_port
();
1177 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1178 $remip = PVE
::Cluster
::remote_node_ip
($node);
1181 # NOTE: kvm VNC traffic is already TLS encrypted
1182 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1189 syslog
('info', "starting vnc proxy $upid\n");
1191 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1193 my $qmstr = join(' ', @$qmcmd);
1195 # also redirect stderr (else we get RFB protocol errors)
1196 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1198 PVE
::Tools
::run_command
($cmd);
1203 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1205 PVE
::Tools
::wait_for_vnc_port
($port);
1216 __PACKAGE__-
>register_method({
1218 path
=> '{vmid}/status',
1221 description
=> "Directory index",
1226 additionalProperties
=> 0,
1228 node
=> get_standard_option
('pve-node'),
1229 vmid
=> get_standard_option
('pve-vmid'),
1237 subdir
=> { type
=> 'string' },
1240 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1246 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1249 { subdir
=> 'current' },
1250 { subdir
=> 'start' },
1251 { subdir
=> 'stop' },
1257 my $vm_is_ha_managed = sub {
1260 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1261 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1267 __PACKAGE__-
>register_method({
1268 name
=> 'vm_status',
1269 path
=> '{vmid}/status/current',
1272 protected
=> 1, # qemu pid files are only readable by root
1273 description
=> "Get virtual machine status.",
1275 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1278 additionalProperties
=> 0,
1280 node
=> get_standard_option
('pve-node'),
1281 vmid
=> get_standard_option
('pve-vmid'),
1284 returns
=> { type
=> 'object' },
1289 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1291 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1292 my $status = $vmstatus->{$param->{vmid
}};
1294 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1299 __PACKAGE__-
>register_method({
1301 path
=> '{vmid}/status/start',
1305 description
=> "Start virtual machine.",
1307 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1310 additionalProperties
=> 0,
1312 node
=> get_standard_option
('pve-node'),
1313 vmid
=> get_standard_option
('pve-vmid'),
1314 skiplock
=> get_standard_option
('skiplock'),
1315 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1316 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1326 my $rpcenv = PVE
::RPCEnvironment
::get
();
1328 my $authuser = $rpcenv->get_user();
1330 my $node = extract_param
($param, 'node');
1332 my $vmid = extract_param
($param, 'vmid');
1334 my $stateuri = extract_param
($param, 'stateuri');
1335 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1336 if $stateuri && $authuser ne 'root@pam';
1338 my $skiplock = extract_param
($param, 'skiplock');
1339 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1340 if $skiplock && $authuser ne 'root@pam';
1342 my $migratedfrom = extract_param
($param, 'migratedfrom');
1343 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1344 if $migratedfrom && $authuser ne 'root@pam';
1346 my $storecfg = PVE
::Storage
::config
();
1348 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1349 $rpcenv->{type
} ne 'ha') {
1354 my $service = "pvevm:$vmid";
1356 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1358 print "Executing HA start for VM $vmid\n";
1360 PVE
::Tools
::run_command
($cmd);
1365 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1372 syslog
('info', "start VM $vmid: $upid\n");
1374 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1379 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1383 __PACKAGE__-
>register_method({
1385 path
=> '{vmid}/status/stop',
1389 description
=> "Stop virtual machine.",
1391 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1394 additionalProperties
=> 0,
1396 node
=> get_standard_option
('pve-node'),
1397 vmid
=> get_standard_option
('pve-vmid'),
1398 skiplock
=> get_standard_option
('skiplock'),
1399 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1401 description
=> "Wait maximal timeout seconds.",
1407 description
=> "Do not decativate storage volumes.",
1420 my $rpcenv = PVE
::RPCEnvironment
::get
();
1422 my $authuser = $rpcenv->get_user();
1424 my $node = extract_param
($param, 'node');
1426 my $vmid = extract_param
($param, 'vmid');
1428 my $skiplock = extract_param
($param, 'skiplock');
1429 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1430 if $skiplock && $authuser ne 'root@pam';
1432 my $keepActive = extract_param
($param, 'keepActive');
1433 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1434 if $keepActive && $authuser ne 'root@pam';
1436 my $migratedfrom = extract_param
($param, 'migratedfrom');
1437 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1438 if $migratedfrom && $authuser ne 'root@pam';
1441 my $storecfg = PVE
::Storage
::config
();
1443 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1448 my $service = "pvevm:$vmid";
1450 my $cmd = ['clusvcadm', '-d', $service];
1452 print "Executing HA stop for VM $vmid\n";
1454 PVE
::Tools
::run_command
($cmd);
1459 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1465 syslog
('info', "stop VM $vmid: $upid\n");
1467 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1468 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1473 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1477 __PACKAGE__-
>register_method({
1479 path
=> '{vmid}/status/reset',
1483 description
=> "Reset virtual machine.",
1485 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1488 additionalProperties
=> 0,
1490 node
=> get_standard_option
('pve-node'),
1491 vmid
=> get_standard_option
('pve-vmid'),
1492 skiplock
=> get_standard_option
('skiplock'),
1501 my $rpcenv = PVE
::RPCEnvironment
::get
();
1503 my $authuser = $rpcenv->get_user();
1505 my $node = extract_param
($param, 'node');
1507 my $vmid = extract_param
($param, 'vmid');
1509 my $skiplock = extract_param
($param, 'skiplock');
1510 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1511 if $skiplock && $authuser ne 'root@pam';
1513 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1518 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1523 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1526 __PACKAGE__-
>register_method({
1527 name
=> 'vm_shutdown',
1528 path
=> '{vmid}/status/shutdown',
1532 description
=> "Shutdown virtual machine.",
1534 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1537 additionalProperties
=> 0,
1539 node
=> get_standard_option
('pve-node'),
1540 vmid
=> get_standard_option
('pve-vmid'),
1541 skiplock
=> get_standard_option
('skiplock'),
1543 description
=> "Wait maximal timeout seconds.",
1549 description
=> "Make sure the VM stops.",
1555 description
=> "Do not decativate storage volumes.",
1568 my $rpcenv = PVE
::RPCEnvironment
::get
();
1570 my $authuser = $rpcenv->get_user();
1572 my $node = extract_param
($param, 'node');
1574 my $vmid = extract_param
($param, 'vmid');
1576 my $skiplock = extract_param
($param, 'skiplock');
1577 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1578 if $skiplock && $authuser ne 'root@pam';
1580 my $keepActive = extract_param
($param, 'keepActive');
1581 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1582 if $keepActive && $authuser ne 'root@pam';
1584 my $storecfg = PVE
::Storage
::config
();
1589 syslog
('info', "shutdown VM $vmid: $upid\n");
1591 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1592 1, $param->{forceStop
}, $keepActive);
1597 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1600 __PACKAGE__-
>register_method({
1601 name
=> 'vm_suspend',
1602 path
=> '{vmid}/status/suspend',
1606 description
=> "Suspend virtual machine.",
1608 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1611 additionalProperties
=> 0,
1613 node
=> get_standard_option
('pve-node'),
1614 vmid
=> get_standard_option
('pve-vmid'),
1615 skiplock
=> get_standard_option
('skiplock'),
1624 my $rpcenv = PVE
::RPCEnvironment
::get
();
1626 my $authuser = $rpcenv->get_user();
1628 my $node = extract_param
($param, 'node');
1630 my $vmid = extract_param
($param, 'vmid');
1632 my $skiplock = extract_param
($param, 'skiplock');
1633 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1634 if $skiplock && $authuser ne 'root@pam';
1636 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1641 syslog
('info', "suspend VM $vmid: $upid\n");
1643 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1648 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1651 __PACKAGE__-
>register_method({
1652 name
=> 'vm_resume',
1653 path
=> '{vmid}/status/resume',
1657 description
=> "Resume virtual machine.",
1659 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1662 additionalProperties
=> 0,
1664 node
=> get_standard_option
('pve-node'),
1665 vmid
=> get_standard_option
('pve-vmid'),
1666 skiplock
=> get_standard_option
('skiplock'),
1675 my $rpcenv = PVE
::RPCEnvironment
::get
();
1677 my $authuser = $rpcenv->get_user();
1679 my $node = extract_param
($param, 'node');
1681 my $vmid = extract_param
($param, 'vmid');
1683 my $skiplock = extract_param
($param, 'skiplock');
1684 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1685 if $skiplock && $authuser ne 'root@pam';
1687 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1692 syslog
('info', "resume VM $vmid: $upid\n");
1694 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1699 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1702 __PACKAGE__-
>register_method({
1703 name
=> 'vm_sendkey',
1704 path
=> '{vmid}/sendkey',
1708 description
=> "Send key event to virtual machine.",
1710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1713 additionalProperties
=> 0,
1715 node
=> get_standard_option
('pve-node'),
1716 vmid
=> get_standard_option
('pve-vmid'),
1717 skiplock
=> get_standard_option
('skiplock'),
1719 description
=> "The key (qemu monitor encoding).",
1724 returns
=> { type
=> 'null'},
1728 my $rpcenv = PVE
::RPCEnvironment
::get
();
1730 my $authuser = $rpcenv->get_user();
1732 my $node = extract_param
($param, 'node');
1734 my $vmid = extract_param
($param, 'vmid');
1736 my $skiplock = extract_param
($param, 'skiplock');
1737 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1738 if $skiplock && $authuser ne 'root@pam';
1740 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1745 __PACKAGE__-
>register_method({
1746 name
=> 'vm_feature',
1747 path
=> '{vmid}/feature',
1751 description
=> "Check if feature for virtual machine is available.",
1753 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1756 additionalProperties
=> 0,
1758 node
=> get_standard_option
('pve-node'),
1759 vmid
=> get_standard_option
('pve-vmid'),
1761 description
=> "Feature to check.",
1763 enum
=> [ 'snapshot', 'clone', 'copy' ],
1765 snapname
=> get_standard_option
('pve-snapshot-name', {
1773 hasFeature
=> { type
=> 'boolean' },
1776 items
=> { type
=> 'string' },
1783 my $node = extract_param
($param, 'node');
1785 my $vmid = extract_param
($param, 'vmid');
1787 my $snapname = extract_param
($param, 'snapname');
1789 my $feature = extract_param
($param, 'feature');
1791 my $running = PVE
::QemuServer
::check_running
($vmid);
1793 my $conf = PVE
::QemuServer
::load_config
($vmid);
1796 my $snap = $conf->{snapshots
}->{$snapname};
1797 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1800 my $storecfg = PVE
::Storage
::config
();
1802 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1803 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1806 hasFeature
=> $hasFeature,
1807 nodes
=> [ keys %$nodelist ],
1811 __PACKAGE__-
>register_method({
1813 path
=> '{vmid}/clone',
1817 description
=> "Create a copy of virtual machine/template.",
1819 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1820 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1821 "'Datastore.AllocateSpace' on any used storage.",
1824 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1826 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1827 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1832 additionalProperties
=> 0,
1834 node
=> get_standard_option
('pve-node'),
1835 vmid
=> get_standard_option
('pve-vmid'),
1836 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1839 type
=> 'string', format
=> 'dns-name',
1840 description
=> "Set a name for the new VM.",
1845 description
=> "Description for the new VM.",
1849 type
=> 'string', format
=> 'pve-poolid',
1850 description
=> "Add the new VM to the specified pool.",
1852 snapname
=> get_standard_option
('pve-snapshot-name', {
1856 storage
=> get_standard_option
('pve-storage-id', {
1857 description
=> "Target storage for full clone.",
1862 description
=> "Target format for file storage.",
1866 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1871 description
=> "Create a full copy of all disk. This is always done when " .
1872 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1875 target
=> get_standard_option
('pve-node', {
1876 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1887 my $rpcenv = PVE
::RPCEnvironment
::get
();
1889 my $authuser = $rpcenv->get_user();
1891 my $node = extract_param
($param, 'node');
1893 my $vmid = extract_param
($param, 'vmid');
1895 my $newid = extract_param
($param, 'newid');
1897 my $pool = extract_param
($param, 'pool');
1899 if (defined($pool)) {
1900 $rpcenv->check_pool_exist($pool);
1903 my $snapname = extract_param
($param, 'snapname');
1905 my $storage = extract_param
($param, 'storage');
1907 my $format = extract_param
($param, 'format');
1909 my $target = extract_param
($param, 'target');
1911 my $localnode = PVE
::INotify
::nodename
();
1913 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1915 PVE
::Cluster
::check_node_exists
($target) if $target;
1917 my $storecfg = PVE
::Storage
::config
();
1920 # check if storage is enabled on local node
1921 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1923 # check if storage is available on target node
1924 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1925 # clone only works if target storage is shared
1926 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1927 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1931 PVE
::Cluster
::check_cfs_quorum
();
1933 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1935 # exclusive lock if VM is running - else shared lock is enough;
1936 my $shared_lock = $running ?
0 : 1;
1940 # do all tests after lock
1941 # we also try to do all tests before we fork the worker
1943 my $conf = PVE
::QemuServer
::load_config
($vmid);
1945 PVE
::QemuServer
::check_lock
($conf);
1947 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1949 die "unexpected state change\n" if $verify_running != $running;
1951 die "snapshot '$snapname' does not exist\n"
1952 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1954 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1956 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1958 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1960 my $conffile = PVE
::QemuServer
::config_file
($newid);
1962 die "unable to create VM $newid: config file already exists\n"
1965 my $newconf = { lock => 'clone' };
1969 foreach my $opt (keys %$oldconf) {
1970 my $value = $oldconf->{$opt};
1972 # do not copy snapshot related info
1973 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1974 $opt eq 'vmstate' || $opt eq 'snapstate';
1976 # always change MAC! address
1977 if ($opt =~ m/^net(\d+)$/) {
1978 my $net = PVE
::QemuServer
::parse_net
($value);
1979 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1980 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1981 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1982 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1983 $newconf->{$opt} = $value; # simply copy configuration
1985 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1986 die "Full clone feature is not available"
1987 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
1990 $drives->{$opt} = $drive;
1991 push @$vollist, $drive->{file
};
1994 # copy everything else
1995 $newconf->{$opt} = $value;
1999 delete $newconf->{template
};
2001 if ($param->{name
}) {
2002 $newconf->{name
} = $param->{name
};
2004 if ($oldconf->{name
}) {
2005 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2007 $newconf->{name
} = "Copy-of-VM-$vmid";
2011 if ($param->{description
}) {
2012 $newconf->{description
} = $param->{description
};
2015 # create empty/temp config - this fails if VM already exists on other node
2016 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2021 my $newvollist = [];
2024 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2026 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2028 foreach my $opt (keys %$drives) {
2029 my $drive = $drives->{$opt};
2031 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2032 $newid, $storage, $format, $drive->{full
}, $newvollist);
2034 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2036 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2039 delete $newconf->{lock};
2040 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2043 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2044 die "Failed to move config to node '$target' - rename failed: $!\n"
2045 if !rename($conffile, $newconffile);
2048 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2053 sleep 1; # some storage like rbd need to wait before release volume - really?
2055 foreach my $volid (@$newvollist) {
2056 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2059 die "clone failed: $err";
2065 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2068 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2069 # Aquire exclusive lock lock for $newid
2070 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2075 __PACKAGE__-
>register_method({
2077 path
=> '{vmid}/move',
2081 description
=> "Move volume to different storage.",
2083 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2086 additionalProperties
=> 0,
2088 node
=> get_standard_option
('pve-node'),
2089 vmid
=> get_standard_option
('pve-vmid'),
2090 skiplock
=> get_standard_option
('skiplock'),
2093 description
=> "The disk you want to move.",
2094 enum
=> [PVE
::QemuServer
::disknames
()],
2098 description
=> "Target Storage.",
2102 description
=> "Target Format.",
2103 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2108 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2114 returns
=> { type
=> 'null'},
2118 my $rpcenv = PVE
::RPCEnvironment
::get
();
2120 my $authuser = $rpcenv->get_user();
2122 my $node = extract_param
($param, 'node');
2124 my $vmid = extract_param
($param, 'vmid');
2126 my $digest = extract_param
($param, 'digest');
2128 my $disk = extract_param
($param, 'disk');
2130 my $storeid = extract_param
($param, 'storage');
2132 my $format = extract_param
($param, 'format');
2134 my $skiplock = extract_param
($param, 'skiplock');
2135 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2136 if $skiplock && $authuser ne 'root@pam';
2138 my $storecfg = PVE
::Storage
::config
();
2140 my $updatefn = sub {
2142 my $conf = PVE
::QemuServer
::load_config
($vmid);
2144 die "checksum missmatch (file change by other user?)\n"
2145 if $digest && $digest ne $conf->{digest
};
2146 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2148 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2150 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2152 my $volid = $drive->{file
};
2154 die "disk '$disk' has no associated volume\n" if !$volid;
2156 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2159 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($volid);
2160 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2164 die "you can't move on the same storage with same format" if ($oldstoreid eq $storeid && (!$format || $oldfmt eq $format));
2166 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2173 $drives->{$disk} = $drive;
2174 push @$vollist, $drive->{file
};
2176 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2178 my $running = PVE
::QemuServer
::check_running
($vmid);
2181 my $newvollist = [];
2184 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2186 &$clone_disks($storecfg, $storeid, $vollist, $newvollist, $drives, undef, $format, $vmid, $vmid, $conf, $running);
2190 foreach my $volid (@$newvollist) {
2191 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2194 die "storage migration failed: $err";
2198 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2200 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2204 __PACKAGE__-
>register_method({
2205 name
=> 'migrate_vm',
2206 path
=> '{vmid}/migrate',
2210 description
=> "Migrate virtual machine. Creates a new migration task.",
2212 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2215 additionalProperties
=> 0,
2217 node
=> get_standard_option
('pve-node'),
2218 vmid
=> get_standard_option
('pve-vmid'),
2219 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2222 description
=> "Use online/live migration.",
2227 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2234 description
=> "the task ID.",
2239 my $rpcenv = PVE
::RPCEnvironment
::get
();
2241 my $authuser = $rpcenv->get_user();
2243 my $target = extract_param
($param, 'target');
2245 my $localnode = PVE
::INotify
::nodename
();
2246 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2248 PVE
::Cluster
::check_cfs_quorum
();
2250 PVE
::Cluster
::check_node_exists
($target);
2252 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2254 my $vmid = extract_param
($param, 'vmid');
2256 raise_param_exc
({ force
=> "Only root may use this option." })
2257 if $param->{force
} && $authuser ne 'root@pam';
2260 my $conf = PVE
::QemuServer
::load_config
($vmid);
2262 # try to detect errors early
2264 PVE
::QemuServer
::check_lock
($conf);
2266 if (PVE
::QemuServer
::check_running
($vmid)) {
2267 die "cant migrate running VM without --online\n"
2268 if !$param->{online
};
2271 my $storecfg = PVE
::Storage
::config
();
2272 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2274 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2279 my $service = "pvevm:$vmid";
2281 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2283 print "Executing HA migrate for VM $vmid to node $target\n";
2285 PVE
::Tools
::run_command
($cmd);
2290 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2297 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2300 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2305 __PACKAGE__-
>register_method({
2307 path
=> '{vmid}/monitor',
2311 description
=> "Execute Qemu monitor commands.",
2313 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2316 additionalProperties
=> 0,
2318 node
=> get_standard_option
('pve-node'),
2319 vmid
=> get_standard_option
('pve-vmid'),
2322 description
=> "The monitor command.",
2326 returns
=> { type
=> 'string'},
2330 my $vmid = $param->{vmid
};
2332 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2336 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2338 $res = "ERROR: $@" if $@;
2343 __PACKAGE__-
>register_method({
2344 name
=> 'resize_vm',
2345 path
=> '{vmid}/resize',
2349 description
=> "Extend volume size.",
2351 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2354 additionalProperties
=> 0,
2356 node
=> get_standard_option
('pve-node'),
2357 vmid
=> get_standard_option
('pve-vmid'),
2358 skiplock
=> get_standard_option
('skiplock'),
2361 description
=> "The disk you want to resize.",
2362 enum
=> [PVE
::QemuServer
::disknames
()],
2366 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2367 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.",
2371 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2377 returns
=> { type
=> 'null'},
2381 my $rpcenv = PVE
::RPCEnvironment
::get
();
2383 my $authuser = $rpcenv->get_user();
2385 my $node = extract_param
($param, 'node');
2387 my $vmid = extract_param
($param, 'vmid');
2389 my $digest = extract_param
($param, 'digest');
2391 my $disk = extract_param
($param, 'disk');
2393 my $sizestr = extract_param
($param, 'size');
2395 my $skiplock = extract_param
($param, 'skiplock');
2396 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2397 if $skiplock && $authuser ne 'root@pam';
2399 my $storecfg = PVE
::Storage
::config
();
2401 my $updatefn = sub {
2403 my $conf = PVE
::QemuServer
::load_config
($vmid);
2405 die "checksum missmatch (file change by other user?)\n"
2406 if $digest && $digest ne $conf->{digest
};
2407 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2409 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2411 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2413 my $volid = $drive->{file
};
2415 die "disk '$disk' has no associated volume\n" if !$volid;
2417 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2419 die "you can't online resize a virtio windows bootdisk\n"
2420 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2422 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2424 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2426 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2428 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2429 my ($ext, $newsize, $unit) = ($1, $2, $4);
2432 $newsize = $newsize * 1024;
2433 } elsif ($unit eq 'M') {
2434 $newsize = $newsize * 1024 * 1024;
2435 } elsif ($unit eq 'G') {
2436 $newsize = $newsize * 1024 * 1024 * 1024;
2437 } elsif ($unit eq 'T') {
2438 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2441 $newsize += $size if $ext;
2442 $newsize = int($newsize);
2444 die "unable to skrink disk size\n" if $newsize < $size;
2446 return if $size == $newsize;
2448 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2450 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2452 $drive->{size
} = $newsize;
2453 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2455 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2458 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2462 __PACKAGE__-
>register_method({
2463 name
=> 'snapshot_list',
2464 path
=> '{vmid}/snapshot',
2466 description
=> "List all snapshots.",
2468 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2471 protected
=> 1, # qemu pid files are only readable by root
2473 additionalProperties
=> 0,
2475 vmid
=> get_standard_option
('pve-vmid'),
2476 node
=> get_standard_option
('pve-node'),
2485 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2490 my $vmid = $param->{vmid
};
2492 my $conf = PVE
::QemuServer
::load_config
($vmid);
2493 my $snaphash = $conf->{snapshots
} || {};
2497 foreach my $name (keys %$snaphash) {
2498 my $d = $snaphash->{$name};
2501 snaptime
=> $d->{snaptime
} || 0,
2502 vmstate
=> $d->{vmstate
} ?
1 : 0,
2503 description
=> $d->{description
} || '',
2505 $item->{parent
} = $d->{parent
} if $d->{parent
};
2506 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2510 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2511 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2512 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2514 push @$res, $current;
2519 __PACKAGE__-
>register_method({
2521 path
=> '{vmid}/snapshot',
2525 description
=> "Snapshot a VM.",
2527 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2530 additionalProperties
=> 0,
2532 node
=> get_standard_option
('pve-node'),
2533 vmid
=> get_standard_option
('pve-vmid'),
2534 snapname
=> get_standard_option
('pve-snapshot-name'),
2538 description
=> "Save the vmstate",
2543 description
=> "Freeze the filesystem",
2548 description
=> "A textual description or comment.",
2554 description
=> "the task ID.",
2559 my $rpcenv = PVE
::RPCEnvironment
::get
();
2561 my $authuser = $rpcenv->get_user();
2563 my $node = extract_param
($param, 'node');
2565 my $vmid = extract_param
($param, 'vmid');
2567 my $snapname = extract_param
($param, 'snapname');
2569 die "unable to use snapshot name 'current' (reserved name)\n"
2570 if $snapname eq 'current';
2573 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2574 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2575 $param->{freezefs
}, $param->{description
});
2578 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2581 __PACKAGE__-
>register_method({
2582 name
=> 'snapshot_cmd_idx',
2583 path
=> '{vmid}/snapshot/{snapname}',
2590 additionalProperties
=> 0,
2592 vmid
=> get_standard_option
('pve-vmid'),
2593 node
=> get_standard_option
('pve-node'),
2594 snapname
=> get_standard_option
('pve-snapshot-name'),
2603 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2610 push @$res, { cmd
=> 'rollback' };
2611 push @$res, { cmd
=> 'config' };
2616 __PACKAGE__-
>register_method({
2617 name
=> 'update_snapshot_config',
2618 path
=> '{vmid}/snapshot/{snapname}/config',
2622 description
=> "Update snapshot metadata.",
2624 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2627 additionalProperties
=> 0,
2629 node
=> get_standard_option
('pve-node'),
2630 vmid
=> get_standard_option
('pve-vmid'),
2631 snapname
=> get_standard_option
('pve-snapshot-name'),
2635 description
=> "A textual description or comment.",
2639 returns
=> { type
=> 'null' },
2643 my $rpcenv = PVE
::RPCEnvironment
::get
();
2645 my $authuser = $rpcenv->get_user();
2647 my $vmid = extract_param
($param, 'vmid');
2649 my $snapname = extract_param
($param, 'snapname');
2651 return undef if !defined($param->{description
});
2653 my $updatefn = sub {
2655 my $conf = PVE
::QemuServer
::load_config
($vmid);
2657 PVE
::QemuServer
::check_lock
($conf);
2659 my $snap = $conf->{snapshots
}->{$snapname};
2661 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2663 $snap->{description
} = $param->{description
} if defined($param->{description
});
2665 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2668 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2673 __PACKAGE__-
>register_method({
2674 name
=> 'get_snapshot_config',
2675 path
=> '{vmid}/snapshot/{snapname}/config',
2678 description
=> "Get snapshot configuration",
2680 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2683 additionalProperties
=> 0,
2685 node
=> get_standard_option
('pve-node'),
2686 vmid
=> get_standard_option
('pve-vmid'),
2687 snapname
=> get_standard_option
('pve-snapshot-name'),
2690 returns
=> { type
=> "object" },
2694 my $rpcenv = PVE
::RPCEnvironment
::get
();
2696 my $authuser = $rpcenv->get_user();
2698 my $vmid = extract_param
($param, 'vmid');
2700 my $snapname = extract_param
($param, 'snapname');
2702 my $conf = PVE
::QemuServer
::load_config
($vmid);
2704 my $snap = $conf->{snapshots
}->{$snapname};
2706 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2711 __PACKAGE__-
>register_method({
2713 path
=> '{vmid}/snapshot/{snapname}/rollback',
2717 description
=> "Rollback VM state to specified snapshot.",
2719 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2722 additionalProperties
=> 0,
2724 node
=> get_standard_option
('pve-node'),
2725 vmid
=> get_standard_option
('pve-vmid'),
2726 snapname
=> get_standard_option
('pve-snapshot-name'),
2731 description
=> "the task ID.",
2736 my $rpcenv = PVE
::RPCEnvironment
::get
();
2738 my $authuser = $rpcenv->get_user();
2740 my $node = extract_param
($param, 'node');
2742 my $vmid = extract_param
($param, 'vmid');
2744 my $snapname = extract_param
($param, 'snapname');
2747 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2748 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2751 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2754 __PACKAGE__-
>register_method({
2755 name
=> 'delsnapshot',
2756 path
=> '{vmid}/snapshot/{snapname}',
2760 description
=> "Delete a VM snapshot.",
2762 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2765 additionalProperties
=> 0,
2767 node
=> get_standard_option
('pve-node'),
2768 vmid
=> get_standard_option
('pve-vmid'),
2769 snapname
=> get_standard_option
('pve-snapshot-name'),
2773 description
=> "For removal from config file, even if removing disk snapshots fails.",
2779 description
=> "the task ID.",
2784 my $rpcenv = PVE
::RPCEnvironment
::get
();
2786 my $authuser = $rpcenv->get_user();
2788 my $node = extract_param
($param, 'node');
2790 my $vmid = extract_param
($param, 'vmid');
2792 my $snapname = extract_param
($param, 'snapname');
2795 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2796 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2799 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2802 __PACKAGE__-
>register_method({
2804 path
=> '{vmid}/template',
2808 description
=> "Create a Template.",
2810 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2811 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2814 additionalProperties
=> 0,
2816 node
=> get_standard_option
('pve-node'),
2817 vmid
=> get_standard_option
('pve-vmid'),
2821 description
=> "If you want to convert only 1 disk to base image.",
2822 enum
=> [PVE
::QemuServer
::disknames
()],
2827 returns
=> { type
=> 'null'},
2831 my $rpcenv = PVE
::RPCEnvironment
::get
();
2833 my $authuser = $rpcenv->get_user();
2835 my $node = extract_param
($param, 'node');
2837 my $vmid = extract_param
($param, 'vmid');
2839 my $disk = extract_param
($param, 'disk');
2841 my $updatefn = sub {
2843 my $conf = PVE
::QemuServer
::load_config
($vmid);
2845 PVE
::QemuServer
::check_lock
($conf);
2847 die "unable to create template, because VM contains snapshots\n"
2848 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2850 die "you can't convert a template to a template\n"
2851 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2853 die "you can't convert a VM to template if VM is running\n"
2854 if PVE
::QemuServer
::check_running
($vmid);
2857 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2860 $conf->{template
} = 1;
2861 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2863 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2866 PVE
::QemuServer
::lock_config
($vmid, $updatefn);