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({
2076 name
=> 'move_vm_disk',
2077 path
=> '{vmid}/move_disk',
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
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2116 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2124 description
=> "the task ID.",
2129 my $rpcenv = PVE
::RPCEnvironment
::get
();
2131 my $authuser = $rpcenv->get_user();
2133 my $node = extract_param
($param, 'node');
2135 my $vmid = extract_param
($param, 'vmid');
2137 my $digest = extract_param
($param, 'digest');
2139 my $disk = extract_param
($param, 'disk');
2141 my $storeid = extract_param
($param, 'storage');
2143 my $format = extract_param
($param, 'format');
2145 my $storecfg = PVE
::Storage
::config
();
2147 my $updatefn = sub {
2149 my $conf = PVE
::QemuServer
::load_config
($vmid);
2151 die "checksum missmatch (file change by other user?)\n"
2152 if $digest && $digest ne $conf->{digest
};
2154 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2156 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2158 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2160 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2163 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2164 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2168 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2169 (!$format || !$oldfmt || $oldfmt eq $format);
2171 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2173 my $running = PVE
::QemuServer
::check_running
($vmid);
2175 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2179 my $newvollist = [];
2182 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2184 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2185 $vmid, $storeid, $format, 1, $newvollist);
2187 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2189 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2191 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2195 foreach my $volid (@$newvollist) {
2196 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2199 die "storage migration failed: $err";
2202 if ($param->{delete}) {
2203 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2208 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2211 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2214 __PACKAGE__-
>register_method({
2215 name
=> 'migrate_vm',
2216 path
=> '{vmid}/migrate',
2220 description
=> "Migrate virtual machine. Creates a new migration task.",
2222 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2225 additionalProperties
=> 0,
2227 node
=> get_standard_option
('pve-node'),
2228 vmid
=> get_standard_option
('pve-vmid'),
2229 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2232 description
=> "Use online/live migration.",
2237 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2244 description
=> "the task ID.",
2249 my $rpcenv = PVE
::RPCEnvironment
::get
();
2251 my $authuser = $rpcenv->get_user();
2253 my $target = extract_param
($param, 'target');
2255 my $localnode = PVE
::INotify
::nodename
();
2256 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2258 PVE
::Cluster
::check_cfs_quorum
();
2260 PVE
::Cluster
::check_node_exists
($target);
2262 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2264 my $vmid = extract_param
($param, 'vmid');
2266 raise_param_exc
({ force
=> "Only root may use this option." })
2267 if $param->{force
} && $authuser ne 'root@pam';
2270 my $conf = PVE
::QemuServer
::load_config
($vmid);
2272 # try to detect errors early
2274 PVE
::QemuServer
::check_lock
($conf);
2276 if (PVE
::QemuServer
::check_running
($vmid)) {
2277 die "cant migrate running VM without --online\n"
2278 if !$param->{online
};
2281 my $storecfg = PVE
::Storage
::config
();
2282 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2284 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2289 my $service = "pvevm:$vmid";
2291 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2293 print "Executing HA migrate for VM $vmid to node $target\n";
2295 PVE
::Tools
::run_command
($cmd);
2300 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2307 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2310 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2315 __PACKAGE__-
>register_method({
2317 path
=> '{vmid}/monitor',
2321 description
=> "Execute Qemu monitor commands.",
2323 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2326 additionalProperties
=> 0,
2328 node
=> get_standard_option
('pve-node'),
2329 vmid
=> get_standard_option
('pve-vmid'),
2332 description
=> "The monitor command.",
2336 returns
=> { type
=> 'string'},
2340 my $vmid = $param->{vmid
};
2342 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2346 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2348 $res = "ERROR: $@" if $@;
2353 __PACKAGE__-
>register_method({
2354 name
=> 'resize_vm',
2355 path
=> '{vmid}/resize',
2359 description
=> "Extend volume size.",
2361 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2364 additionalProperties
=> 0,
2366 node
=> get_standard_option
('pve-node'),
2367 vmid
=> get_standard_option
('pve-vmid'),
2368 skiplock
=> get_standard_option
('skiplock'),
2371 description
=> "The disk you want to resize.",
2372 enum
=> [PVE
::QemuServer
::disknames
()],
2376 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2377 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.",
2381 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2387 returns
=> { type
=> 'null'},
2391 my $rpcenv = PVE
::RPCEnvironment
::get
();
2393 my $authuser = $rpcenv->get_user();
2395 my $node = extract_param
($param, 'node');
2397 my $vmid = extract_param
($param, 'vmid');
2399 my $digest = extract_param
($param, 'digest');
2401 my $disk = extract_param
($param, 'disk');
2403 my $sizestr = extract_param
($param, 'size');
2405 my $skiplock = extract_param
($param, 'skiplock');
2406 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2407 if $skiplock && $authuser ne 'root@pam';
2409 my $storecfg = PVE
::Storage
::config
();
2411 my $updatefn = sub {
2413 my $conf = PVE
::QemuServer
::load_config
($vmid);
2415 die "checksum missmatch (file change by other user?)\n"
2416 if $digest && $digest ne $conf->{digest
};
2417 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2419 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2421 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2423 my $volid = $drive->{file
};
2425 die "disk '$disk' has no associated volume\n" if !$volid;
2427 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2429 die "you can't online resize a virtio windows bootdisk\n"
2430 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2432 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2434 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2436 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2438 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2439 my ($ext, $newsize, $unit) = ($1, $2, $4);
2442 $newsize = $newsize * 1024;
2443 } elsif ($unit eq 'M') {
2444 $newsize = $newsize * 1024 * 1024;
2445 } elsif ($unit eq 'G') {
2446 $newsize = $newsize * 1024 * 1024 * 1024;
2447 } elsif ($unit eq 'T') {
2448 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2451 $newsize += $size if $ext;
2452 $newsize = int($newsize);
2454 die "unable to skrink disk size\n" if $newsize < $size;
2456 return if $size == $newsize;
2458 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2460 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2462 $drive->{size
} = $newsize;
2463 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2465 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2468 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2472 __PACKAGE__-
>register_method({
2473 name
=> 'snapshot_list',
2474 path
=> '{vmid}/snapshot',
2476 description
=> "List all snapshots.",
2478 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2481 protected
=> 1, # qemu pid files are only readable by root
2483 additionalProperties
=> 0,
2485 vmid
=> get_standard_option
('pve-vmid'),
2486 node
=> get_standard_option
('pve-node'),
2495 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2500 my $vmid = $param->{vmid
};
2502 my $conf = PVE
::QemuServer
::load_config
($vmid);
2503 my $snaphash = $conf->{snapshots
} || {};
2507 foreach my $name (keys %$snaphash) {
2508 my $d = $snaphash->{$name};
2511 snaptime
=> $d->{snaptime
} || 0,
2512 vmstate
=> $d->{vmstate
} ?
1 : 0,
2513 description
=> $d->{description
} || '',
2515 $item->{parent
} = $d->{parent
} if $d->{parent
};
2516 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2520 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2521 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2522 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2524 push @$res, $current;
2529 __PACKAGE__-
>register_method({
2531 path
=> '{vmid}/snapshot',
2535 description
=> "Snapshot a VM.",
2537 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2540 additionalProperties
=> 0,
2542 node
=> get_standard_option
('pve-node'),
2543 vmid
=> get_standard_option
('pve-vmid'),
2544 snapname
=> get_standard_option
('pve-snapshot-name'),
2548 description
=> "Save the vmstate",
2553 description
=> "Freeze the filesystem",
2558 description
=> "A textual description or comment.",
2564 description
=> "the task ID.",
2569 my $rpcenv = PVE
::RPCEnvironment
::get
();
2571 my $authuser = $rpcenv->get_user();
2573 my $node = extract_param
($param, 'node');
2575 my $vmid = extract_param
($param, 'vmid');
2577 my $snapname = extract_param
($param, 'snapname');
2579 die "unable to use snapshot name 'current' (reserved name)\n"
2580 if $snapname eq 'current';
2583 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2584 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2585 $param->{freezefs
}, $param->{description
});
2588 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2591 __PACKAGE__-
>register_method({
2592 name
=> 'snapshot_cmd_idx',
2593 path
=> '{vmid}/snapshot/{snapname}',
2600 additionalProperties
=> 0,
2602 vmid
=> get_standard_option
('pve-vmid'),
2603 node
=> get_standard_option
('pve-node'),
2604 snapname
=> get_standard_option
('pve-snapshot-name'),
2613 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2620 push @$res, { cmd
=> 'rollback' };
2621 push @$res, { cmd
=> 'config' };
2626 __PACKAGE__-
>register_method({
2627 name
=> 'update_snapshot_config',
2628 path
=> '{vmid}/snapshot/{snapname}/config',
2632 description
=> "Update snapshot metadata.",
2634 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2637 additionalProperties
=> 0,
2639 node
=> get_standard_option
('pve-node'),
2640 vmid
=> get_standard_option
('pve-vmid'),
2641 snapname
=> get_standard_option
('pve-snapshot-name'),
2645 description
=> "A textual description or comment.",
2649 returns
=> { type
=> 'null' },
2653 my $rpcenv = PVE
::RPCEnvironment
::get
();
2655 my $authuser = $rpcenv->get_user();
2657 my $vmid = extract_param
($param, 'vmid');
2659 my $snapname = extract_param
($param, 'snapname');
2661 return undef if !defined($param->{description
});
2663 my $updatefn = sub {
2665 my $conf = PVE
::QemuServer
::load_config
($vmid);
2667 PVE
::QemuServer
::check_lock
($conf);
2669 my $snap = $conf->{snapshots
}->{$snapname};
2671 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2673 $snap->{description
} = $param->{description
} if defined($param->{description
});
2675 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2678 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2683 __PACKAGE__-
>register_method({
2684 name
=> 'get_snapshot_config',
2685 path
=> '{vmid}/snapshot/{snapname}/config',
2688 description
=> "Get snapshot configuration",
2690 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2693 additionalProperties
=> 0,
2695 node
=> get_standard_option
('pve-node'),
2696 vmid
=> get_standard_option
('pve-vmid'),
2697 snapname
=> get_standard_option
('pve-snapshot-name'),
2700 returns
=> { type
=> "object" },
2704 my $rpcenv = PVE
::RPCEnvironment
::get
();
2706 my $authuser = $rpcenv->get_user();
2708 my $vmid = extract_param
($param, 'vmid');
2710 my $snapname = extract_param
($param, 'snapname');
2712 my $conf = PVE
::QemuServer
::load_config
($vmid);
2714 my $snap = $conf->{snapshots
}->{$snapname};
2716 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2721 __PACKAGE__-
>register_method({
2723 path
=> '{vmid}/snapshot/{snapname}/rollback',
2727 description
=> "Rollback VM state to specified snapshot.",
2729 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2732 additionalProperties
=> 0,
2734 node
=> get_standard_option
('pve-node'),
2735 vmid
=> get_standard_option
('pve-vmid'),
2736 snapname
=> get_standard_option
('pve-snapshot-name'),
2741 description
=> "the task ID.",
2746 my $rpcenv = PVE
::RPCEnvironment
::get
();
2748 my $authuser = $rpcenv->get_user();
2750 my $node = extract_param
($param, 'node');
2752 my $vmid = extract_param
($param, 'vmid');
2754 my $snapname = extract_param
($param, 'snapname');
2757 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2758 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2761 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2764 __PACKAGE__-
>register_method({
2765 name
=> 'delsnapshot',
2766 path
=> '{vmid}/snapshot/{snapname}',
2770 description
=> "Delete a VM snapshot.",
2772 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2775 additionalProperties
=> 0,
2777 node
=> get_standard_option
('pve-node'),
2778 vmid
=> get_standard_option
('pve-vmid'),
2779 snapname
=> get_standard_option
('pve-snapshot-name'),
2783 description
=> "For removal from config file, even if removing disk snapshots fails.",
2789 description
=> "the task ID.",
2794 my $rpcenv = PVE
::RPCEnvironment
::get
();
2796 my $authuser = $rpcenv->get_user();
2798 my $node = extract_param
($param, 'node');
2800 my $vmid = extract_param
($param, 'vmid');
2802 my $snapname = extract_param
($param, 'snapname');
2805 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2806 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2809 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2812 __PACKAGE__-
>register_method({
2814 path
=> '{vmid}/template',
2818 description
=> "Create a Template.",
2820 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2821 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2824 additionalProperties
=> 0,
2826 node
=> get_standard_option
('pve-node'),
2827 vmid
=> get_standard_option
('pve-vmid'),
2831 description
=> "If you want to convert only 1 disk to base image.",
2832 enum
=> [PVE
::QemuServer
::disknames
()],
2837 returns
=> { type
=> 'null'},
2841 my $rpcenv = PVE
::RPCEnvironment
::get
();
2843 my $authuser = $rpcenv->get_user();
2845 my $node = extract_param
($param, 'node');
2847 my $vmid = extract_param
($param, 'vmid');
2849 my $disk = extract_param
($param, 'disk');
2851 my $updatefn = sub {
2853 my $conf = PVE
::QemuServer
::load_config
($vmid);
2855 PVE
::QemuServer
::check_lock
($conf);
2857 die "unable to create template, because VM contains snapshots\n"
2858 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2860 die "you can't convert a template to a template\n"
2861 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2863 die "you can't convert a VM to template if VM is running\n"
2864 if PVE
::QemuServer
::check_running
($vmid);
2867 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2870 $conf->{template
} = 1;
2871 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2873 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2876 PVE
::QemuServer
::lock_config
($vmid, $updatefn);