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 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2084 "and 'Datastore.AllocateSpace' permissions on the storage.",
2087 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2088 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2092 additionalProperties
=> 0,
2094 node
=> get_standard_option
('pve-node'),
2095 vmid
=> get_standard_option
('pve-vmid'),
2098 description
=> "The disk you want to move.",
2099 enum
=> [ PVE
::QemuServer
::disknames
() ],
2101 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2104 description
=> "Target Format.",
2105 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2110 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2118 description
=> "the task ID.",
2123 my $rpcenv = PVE
::RPCEnvironment
::get
();
2125 my $authuser = $rpcenv->get_user();
2127 my $node = extract_param
($param, 'node');
2129 my $vmid = extract_param
($param, 'vmid');
2131 my $digest = extract_param
($param, 'digest');
2133 my $disk = extract_param
($param, 'disk');
2135 my $storeid = extract_param
($param, 'storage');
2137 my $format = extract_param
($param, 'format');
2139 my $storecfg = PVE
::Storage
::config
();
2141 my $updatefn = sub {
2143 my $conf = PVE
::QemuServer
::load_config
($vmid);
2145 die "checksum missmatch (file change by other user?)\n"
2146 if $digest && $digest ne $conf->{digest
};
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
} || die "disk '$disk' has no associated volume\n";
2154 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2157 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($volid);
2158 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2162 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2163 (!$format || !$oldfmt || $oldfmt eq $format);
2165 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2167 my $running = PVE
::QemuServer
::check_running
($vmid);
2169 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2173 my $newvollist = [];
2176 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2178 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2179 $vmid, $storeid, $format, 1, $newvollist);
2181 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2183 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2187 foreach my $volid (@$newvollist) {
2188 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2191 die "storage migration failed: $err";
2195 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2198 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2201 __PACKAGE__-
>register_method({
2202 name
=> 'migrate_vm',
2203 path
=> '{vmid}/migrate',
2207 description
=> "Migrate virtual machine. Creates a new migration task.",
2209 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2212 additionalProperties
=> 0,
2214 node
=> get_standard_option
('pve-node'),
2215 vmid
=> get_standard_option
('pve-vmid'),
2216 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2219 description
=> "Use online/live migration.",
2224 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2231 description
=> "the task ID.",
2236 my $rpcenv = PVE
::RPCEnvironment
::get
();
2238 my $authuser = $rpcenv->get_user();
2240 my $target = extract_param
($param, 'target');
2242 my $localnode = PVE
::INotify
::nodename
();
2243 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2245 PVE
::Cluster
::check_cfs_quorum
();
2247 PVE
::Cluster
::check_node_exists
($target);
2249 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2251 my $vmid = extract_param
($param, 'vmid');
2253 raise_param_exc
({ force
=> "Only root may use this option." })
2254 if $param->{force
} && $authuser ne 'root@pam';
2257 my $conf = PVE
::QemuServer
::load_config
($vmid);
2259 # try to detect errors early
2261 PVE
::QemuServer
::check_lock
($conf);
2263 if (PVE
::QemuServer
::check_running
($vmid)) {
2264 die "cant migrate running VM without --online\n"
2265 if !$param->{online
};
2268 my $storecfg = PVE
::Storage
::config
();
2269 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2271 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2276 my $service = "pvevm:$vmid";
2278 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2280 print "Executing HA migrate for VM $vmid to node $target\n";
2282 PVE
::Tools
::run_command
($cmd);
2287 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2294 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2297 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2302 __PACKAGE__-
>register_method({
2304 path
=> '{vmid}/monitor',
2308 description
=> "Execute Qemu monitor commands.",
2310 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2313 additionalProperties
=> 0,
2315 node
=> get_standard_option
('pve-node'),
2316 vmid
=> get_standard_option
('pve-vmid'),
2319 description
=> "The monitor command.",
2323 returns
=> { type
=> 'string'},
2327 my $vmid = $param->{vmid
};
2329 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2333 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2335 $res = "ERROR: $@" if $@;
2340 __PACKAGE__-
>register_method({
2341 name
=> 'resize_vm',
2342 path
=> '{vmid}/resize',
2346 description
=> "Extend volume size.",
2348 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2351 additionalProperties
=> 0,
2353 node
=> get_standard_option
('pve-node'),
2354 vmid
=> get_standard_option
('pve-vmid'),
2355 skiplock
=> get_standard_option
('skiplock'),
2358 description
=> "The disk you want to resize.",
2359 enum
=> [PVE
::QemuServer
::disknames
()],
2363 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2364 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.",
2368 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2374 returns
=> { type
=> 'null'},
2378 my $rpcenv = PVE
::RPCEnvironment
::get
();
2380 my $authuser = $rpcenv->get_user();
2382 my $node = extract_param
($param, 'node');
2384 my $vmid = extract_param
($param, 'vmid');
2386 my $digest = extract_param
($param, 'digest');
2388 my $disk = extract_param
($param, 'disk');
2390 my $sizestr = extract_param
($param, 'size');
2392 my $skiplock = extract_param
($param, 'skiplock');
2393 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2394 if $skiplock && $authuser ne 'root@pam';
2396 my $storecfg = PVE
::Storage
::config
();
2398 my $updatefn = sub {
2400 my $conf = PVE
::QemuServer
::load_config
($vmid);
2402 die "checksum missmatch (file change by other user?)\n"
2403 if $digest && $digest ne $conf->{digest
};
2404 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2406 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2408 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2410 my $volid = $drive->{file
};
2412 die "disk '$disk' has no associated volume\n" if !$volid;
2414 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2416 die "you can't online resize a virtio windows bootdisk\n"
2417 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2419 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2421 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2423 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2425 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2426 my ($ext, $newsize, $unit) = ($1, $2, $4);
2429 $newsize = $newsize * 1024;
2430 } elsif ($unit eq 'M') {
2431 $newsize = $newsize * 1024 * 1024;
2432 } elsif ($unit eq 'G') {
2433 $newsize = $newsize * 1024 * 1024 * 1024;
2434 } elsif ($unit eq 'T') {
2435 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2438 $newsize += $size if $ext;
2439 $newsize = int($newsize);
2441 die "unable to skrink disk size\n" if $newsize < $size;
2443 return if $size == $newsize;
2445 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2447 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2449 $drive->{size
} = $newsize;
2450 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2452 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2455 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2459 __PACKAGE__-
>register_method({
2460 name
=> 'snapshot_list',
2461 path
=> '{vmid}/snapshot',
2463 description
=> "List all snapshots.",
2465 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2468 protected
=> 1, # qemu pid files are only readable by root
2470 additionalProperties
=> 0,
2472 vmid
=> get_standard_option
('pve-vmid'),
2473 node
=> get_standard_option
('pve-node'),
2482 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2487 my $vmid = $param->{vmid
};
2489 my $conf = PVE
::QemuServer
::load_config
($vmid);
2490 my $snaphash = $conf->{snapshots
} || {};
2494 foreach my $name (keys %$snaphash) {
2495 my $d = $snaphash->{$name};
2498 snaptime
=> $d->{snaptime
} || 0,
2499 vmstate
=> $d->{vmstate
} ?
1 : 0,
2500 description
=> $d->{description
} || '',
2502 $item->{parent
} = $d->{parent
} if $d->{parent
};
2503 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2507 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2508 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2509 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2511 push @$res, $current;
2516 __PACKAGE__-
>register_method({
2518 path
=> '{vmid}/snapshot',
2522 description
=> "Snapshot a VM.",
2524 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2527 additionalProperties
=> 0,
2529 node
=> get_standard_option
('pve-node'),
2530 vmid
=> get_standard_option
('pve-vmid'),
2531 snapname
=> get_standard_option
('pve-snapshot-name'),
2535 description
=> "Save the vmstate",
2540 description
=> "Freeze the filesystem",
2545 description
=> "A textual description or comment.",
2551 description
=> "the task ID.",
2556 my $rpcenv = PVE
::RPCEnvironment
::get
();
2558 my $authuser = $rpcenv->get_user();
2560 my $node = extract_param
($param, 'node');
2562 my $vmid = extract_param
($param, 'vmid');
2564 my $snapname = extract_param
($param, 'snapname');
2566 die "unable to use snapshot name 'current' (reserved name)\n"
2567 if $snapname eq 'current';
2570 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2571 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2572 $param->{freezefs
}, $param->{description
});
2575 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2578 __PACKAGE__-
>register_method({
2579 name
=> 'snapshot_cmd_idx',
2580 path
=> '{vmid}/snapshot/{snapname}',
2587 additionalProperties
=> 0,
2589 vmid
=> get_standard_option
('pve-vmid'),
2590 node
=> get_standard_option
('pve-node'),
2591 snapname
=> get_standard_option
('pve-snapshot-name'),
2600 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2607 push @$res, { cmd
=> 'rollback' };
2608 push @$res, { cmd
=> 'config' };
2613 __PACKAGE__-
>register_method({
2614 name
=> 'update_snapshot_config',
2615 path
=> '{vmid}/snapshot/{snapname}/config',
2619 description
=> "Update snapshot metadata.",
2621 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2624 additionalProperties
=> 0,
2626 node
=> get_standard_option
('pve-node'),
2627 vmid
=> get_standard_option
('pve-vmid'),
2628 snapname
=> get_standard_option
('pve-snapshot-name'),
2632 description
=> "A textual description or comment.",
2636 returns
=> { type
=> 'null' },
2640 my $rpcenv = PVE
::RPCEnvironment
::get
();
2642 my $authuser = $rpcenv->get_user();
2644 my $vmid = extract_param
($param, 'vmid');
2646 my $snapname = extract_param
($param, 'snapname');
2648 return undef if !defined($param->{description
});
2650 my $updatefn = sub {
2652 my $conf = PVE
::QemuServer
::load_config
($vmid);
2654 PVE
::QemuServer
::check_lock
($conf);
2656 my $snap = $conf->{snapshots
}->{$snapname};
2658 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2660 $snap->{description
} = $param->{description
} if defined($param->{description
});
2662 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2665 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2670 __PACKAGE__-
>register_method({
2671 name
=> 'get_snapshot_config',
2672 path
=> '{vmid}/snapshot/{snapname}/config',
2675 description
=> "Get snapshot configuration",
2677 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2680 additionalProperties
=> 0,
2682 node
=> get_standard_option
('pve-node'),
2683 vmid
=> get_standard_option
('pve-vmid'),
2684 snapname
=> get_standard_option
('pve-snapshot-name'),
2687 returns
=> { type
=> "object" },
2691 my $rpcenv = PVE
::RPCEnvironment
::get
();
2693 my $authuser = $rpcenv->get_user();
2695 my $vmid = extract_param
($param, 'vmid');
2697 my $snapname = extract_param
($param, 'snapname');
2699 my $conf = PVE
::QemuServer
::load_config
($vmid);
2701 my $snap = $conf->{snapshots
}->{$snapname};
2703 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2708 __PACKAGE__-
>register_method({
2710 path
=> '{vmid}/snapshot/{snapname}/rollback',
2714 description
=> "Rollback VM state to specified snapshot.",
2716 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2719 additionalProperties
=> 0,
2721 node
=> get_standard_option
('pve-node'),
2722 vmid
=> get_standard_option
('pve-vmid'),
2723 snapname
=> get_standard_option
('pve-snapshot-name'),
2728 description
=> "the task ID.",
2733 my $rpcenv = PVE
::RPCEnvironment
::get
();
2735 my $authuser = $rpcenv->get_user();
2737 my $node = extract_param
($param, 'node');
2739 my $vmid = extract_param
($param, 'vmid');
2741 my $snapname = extract_param
($param, 'snapname');
2744 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2745 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2748 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2751 __PACKAGE__-
>register_method({
2752 name
=> 'delsnapshot',
2753 path
=> '{vmid}/snapshot/{snapname}',
2757 description
=> "Delete a VM snapshot.",
2759 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2762 additionalProperties
=> 0,
2764 node
=> get_standard_option
('pve-node'),
2765 vmid
=> get_standard_option
('pve-vmid'),
2766 snapname
=> get_standard_option
('pve-snapshot-name'),
2770 description
=> "For removal from config file, even if removing disk snapshots fails.",
2776 description
=> "the task ID.",
2781 my $rpcenv = PVE
::RPCEnvironment
::get
();
2783 my $authuser = $rpcenv->get_user();
2785 my $node = extract_param
($param, 'node');
2787 my $vmid = extract_param
($param, 'vmid');
2789 my $snapname = extract_param
($param, 'snapname');
2792 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2793 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2796 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2799 __PACKAGE__-
>register_method({
2801 path
=> '{vmid}/template',
2805 description
=> "Create a Template.",
2807 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2808 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2811 additionalProperties
=> 0,
2813 node
=> get_standard_option
('pve-node'),
2814 vmid
=> get_standard_option
('pve-vmid'),
2818 description
=> "If you want to convert only 1 disk to base image.",
2819 enum
=> [PVE
::QemuServer
::disknames
()],
2824 returns
=> { type
=> 'null'},
2828 my $rpcenv = PVE
::RPCEnvironment
::get
();
2830 my $authuser = $rpcenv->get_user();
2832 my $node = extract_param
($param, 'node');
2834 my $vmid = extract_param
($param, 'vmid');
2836 my $disk = extract_param
($param, 'disk');
2838 my $updatefn = sub {
2840 my $conf = PVE
::QemuServer
::load_config
($vmid);
2842 PVE
::QemuServer
::check_lock
($conf);
2844 die "unable to create template, because VM contains snapshots\n"
2845 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2847 die "you can't convert a template to a template\n"
2848 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2850 die "you can't convert a VM to template if VM is running\n"
2851 if PVE
::QemuServer
::check_running
($vmid);
2854 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2857 $conf->{template
} = 1;
2858 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2860 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2863 PVE
::QemuServer
::lock_config
($vmid, $updatefn);