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' || $opt eq 'machine' ||
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 }),
1317 machine
=> get_standard_option
('pve-qm-machine'),
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 $machine = extract_param
($param, 'machine');
1336 my $stateuri = extract_param
($param, 'stateuri');
1337 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1338 if $stateuri && $authuser ne 'root@pam';
1340 my $skiplock = extract_param
($param, 'skiplock');
1341 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1342 if $skiplock && $authuser ne 'root@pam';
1344 my $migratedfrom = extract_param
($param, 'migratedfrom');
1345 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1346 if $migratedfrom && $authuser ne 'root@pam';
1348 my $storecfg = PVE
::Storage
::config
();
1350 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1351 $rpcenv->{type
} ne 'ha') {
1356 my $service = "pvevm:$vmid";
1358 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1360 print "Executing HA start for VM $vmid\n";
1362 PVE
::Tools
::run_command
($cmd);
1367 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1374 syslog
('info', "start VM $vmid: $upid\n");
1376 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
1381 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1385 __PACKAGE__-
>register_method({
1387 path
=> '{vmid}/status/stop',
1391 description
=> "Stop virtual machine.",
1393 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1396 additionalProperties
=> 0,
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid'),
1400 skiplock
=> get_standard_option
('skiplock'),
1401 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1403 description
=> "Wait maximal timeout seconds.",
1409 description
=> "Do not decativate storage volumes.",
1422 my $rpcenv = PVE
::RPCEnvironment
::get
();
1424 my $authuser = $rpcenv->get_user();
1426 my $node = extract_param
($param, 'node');
1428 my $vmid = extract_param
($param, 'vmid');
1430 my $skiplock = extract_param
($param, 'skiplock');
1431 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1432 if $skiplock && $authuser ne 'root@pam';
1434 my $keepActive = extract_param
($param, 'keepActive');
1435 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1436 if $keepActive && $authuser ne 'root@pam';
1438 my $migratedfrom = extract_param
($param, 'migratedfrom');
1439 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1440 if $migratedfrom && $authuser ne 'root@pam';
1443 my $storecfg = PVE
::Storage
::config
();
1445 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1450 my $service = "pvevm:$vmid";
1452 my $cmd = ['clusvcadm', '-d', $service];
1454 print "Executing HA stop for VM $vmid\n";
1456 PVE
::Tools
::run_command
($cmd);
1461 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1467 syslog
('info', "stop VM $vmid: $upid\n");
1469 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1470 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1475 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1479 __PACKAGE__-
>register_method({
1481 path
=> '{vmid}/status/reset',
1485 description
=> "Reset virtual machine.",
1487 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1490 additionalProperties
=> 0,
1492 node
=> get_standard_option
('pve-node'),
1493 vmid
=> get_standard_option
('pve-vmid'),
1494 skiplock
=> get_standard_option
('skiplock'),
1503 my $rpcenv = PVE
::RPCEnvironment
::get
();
1505 my $authuser = $rpcenv->get_user();
1507 my $node = extract_param
($param, 'node');
1509 my $vmid = extract_param
($param, 'vmid');
1511 my $skiplock = extract_param
($param, 'skiplock');
1512 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1513 if $skiplock && $authuser ne 'root@pam';
1515 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1520 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1525 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1528 __PACKAGE__-
>register_method({
1529 name
=> 'vm_shutdown',
1530 path
=> '{vmid}/status/shutdown',
1534 description
=> "Shutdown virtual machine.",
1536 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1539 additionalProperties
=> 0,
1541 node
=> get_standard_option
('pve-node'),
1542 vmid
=> get_standard_option
('pve-vmid'),
1543 skiplock
=> get_standard_option
('skiplock'),
1545 description
=> "Wait maximal timeout seconds.",
1551 description
=> "Make sure the VM stops.",
1557 description
=> "Do not decativate storage volumes.",
1570 my $rpcenv = PVE
::RPCEnvironment
::get
();
1572 my $authuser = $rpcenv->get_user();
1574 my $node = extract_param
($param, 'node');
1576 my $vmid = extract_param
($param, 'vmid');
1578 my $skiplock = extract_param
($param, 'skiplock');
1579 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1580 if $skiplock && $authuser ne 'root@pam';
1582 my $keepActive = extract_param
($param, 'keepActive');
1583 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1584 if $keepActive && $authuser ne 'root@pam';
1586 my $storecfg = PVE
::Storage
::config
();
1591 syslog
('info', "shutdown VM $vmid: $upid\n");
1593 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1594 1, $param->{forceStop
}, $keepActive);
1599 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1602 __PACKAGE__-
>register_method({
1603 name
=> 'vm_suspend',
1604 path
=> '{vmid}/status/suspend',
1608 description
=> "Suspend virtual machine.",
1610 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1613 additionalProperties
=> 0,
1615 node
=> get_standard_option
('pve-node'),
1616 vmid
=> get_standard_option
('pve-vmid'),
1617 skiplock
=> get_standard_option
('skiplock'),
1626 my $rpcenv = PVE
::RPCEnvironment
::get
();
1628 my $authuser = $rpcenv->get_user();
1630 my $node = extract_param
($param, 'node');
1632 my $vmid = extract_param
($param, 'vmid');
1634 my $skiplock = extract_param
($param, 'skiplock');
1635 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1636 if $skiplock && $authuser ne 'root@pam';
1638 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1643 syslog
('info', "suspend VM $vmid: $upid\n");
1645 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1650 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1653 __PACKAGE__-
>register_method({
1654 name
=> 'vm_resume',
1655 path
=> '{vmid}/status/resume',
1659 description
=> "Resume virtual machine.",
1661 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1664 additionalProperties
=> 0,
1666 node
=> get_standard_option
('pve-node'),
1667 vmid
=> get_standard_option
('pve-vmid'),
1668 skiplock
=> get_standard_option
('skiplock'),
1677 my $rpcenv = PVE
::RPCEnvironment
::get
();
1679 my $authuser = $rpcenv->get_user();
1681 my $node = extract_param
($param, 'node');
1683 my $vmid = extract_param
($param, 'vmid');
1685 my $skiplock = extract_param
($param, 'skiplock');
1686 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1687 if $skiplock && $authuser ne 'root@pam';
1689 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1694 syslog
('info', "resume VM $vmid: $upid\n");
1696 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1701 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1704 __PACKAGE__-
>register_method({
1705 name
=> 'vm_sendkey',
1706 path
=> '{vmid}/sendkey',
1710 description
=> "Send key event to virtual machine.",
1712 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1715 additionalProperties
=> 0,
1717 node
=> get_standard_option
('pve-node'),
1718 vmid
=> get_standard_option
('pve-vmid'),
1719 skiplock
=> get_standard_option
('skiplock'),
1721 description
=> "The key (qemu monitor encoding).",
1726 returns
=> { type
=> 'null'},
1730 my $rpcenv = PVE
::RPCEnvironment
::get
();
1732 my $authuser = $rpcenv->get_user();
1734 my $node = extract_param
($param, 'node');
1736 my $vmid = extract_param
($param, 'vmid');
1738 my $skiplock = extract_param
($param, 'skiplock');
1739 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1740 if $skiplock && $authuser ne 'root@pam';
1742 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1747 __PACKAGE__-
>register_method({
1748 name
=> 'vm_feature',
1749 path
=> '{vmid}/feature',
1753 description
=> "Check if feature for virtual machine is available.",
1755 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1758 additionalProperties
=> 0,
1760 node
=> get_standard_option
('pve-node'),
1761 vmid
=> get_standard_option
('pve-vmid'),
1763 description
=> "Feature to check.",
1765 enum
=> [ 'snapshot', 'clone', 'copy' ],
1767 snapname
=> get_standard_option
('pve-snapshot-name', {
1775 hasFeature
=> { type
=> 'boolean' },
1778 items
=> { type
=> 'string' },
1785 my $node = extract_param
($param, 'node');
1787 my $vmid = extract_param
($param, 'vmid');
1789 my $snapname = extract_param
($param, 'snapname');
1791 my $feature = extract_param
($param, 'feature');
1793 my $running = PVE
::QemuServer
::check_running
($vmid);
1795 my $conf = PVE
::QemuServer
::load_config
($vmid);
1798 my $snap = $conf->{snapshots
}->{$snapname};
1799 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1802 my $storecfg = PVE
::Storage
::config
();
1804 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
1805 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1808 hasFeature
=> $hasFeature,
1809 nodes
=> [ keys %$nodelist ],
1813 __PACKAGE__-
>register_method({
1815 path
=> '{vmid}/clone',
1819 description
=> "Create a copy of virtual machine/template.",
1821 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
1822 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1823 "'Datastore.AllocateSpace' on any used storage.",
1826 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1828 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1829 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1834 additionalProperties
=> 0,
1836 node
=> get_standard_option
('pve-node'),
1837 vmid
=> get_standard_option
('pve-vmid'),
1838 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
1841 type
=> 'string', format
=> 'dns-name',
1842 description
=> "Set a name for the new VM.",
1847 description
=> "Description for the new VM.",
1851 type
=> 'string', format
=> 'pve-poolid',
1852 description
=> "Add the new VM to the specified pool.",
1854 snapname
=> get_standard_option
('pve-snapshot-name', {
1858 storage
=> get_standard_option
('pve-storage-id', {
1859 description
=> "Target storage for full clone.",
1864 description
=> "Target format for file storage.",
1868 enum
=> [ 'raw', 'qcow2', 'vmdk'],
1873 description
=> "Create a full copy of all disk. This is always done when " .
1874 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
1877 target
=> get_standard_option
('pve-node', {
1878 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1889 my $rpcenv = PVE
::RPCEnvironment
::get
();
1891 my $authuser = $rpcenv->get_user();
1893 my $node = extract_param
($param, 'node');
1895 my $vmid = extract_param
($param, 'vmid');
1897 my $newid = extract_param
($param, 'newid');
1899 my $pool = extract_param
($param, 'pool');
1901 if (defined($pool)) {
1902 $rpcenv->check_pool_exist($pool);
1905 my $snapname = extract_param
($param, 'snapname');
1907 my $storage = extract_param
($param, 'storage');
1909 my $format = extract_param
($param, 'format');
1911 my $target = extract_param
($param, 'target');
1913 my $localnode = PVE
::INotify
::nodename
();
1915 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1917 PVE
::Cluster
::check_node_exists
($target) if $target;
1919 my $storecfg = PVE
::Storage
::config
();
1922 # check if storage is enabled on local node
1923 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1925 # check if storage is available on target node
1926 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1927 # clone only works if target storage is shared
1928 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1929 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1933 PVE
::Cluster
::check_cfs_quorum
();
1935 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
1937 # exclusive lock if VM is running - else shared lock is enough;
1938 my $shared_lock = $running ?
0 : 1;
1942 # do all tests after lock
1943 # we also try to do all tests before we fork the worker
1945 my $conf = PVE
::QemuServer
::load_config
($vmid);
1947 PVE
::QemuServer
::check_lock
($conf);
1949 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
1951 die "unexpected state change\n" if $verify_running != $running;
1953 die "snapshot '$snapname' does not exist\n"
1954 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1956 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1958 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
1960 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
1962 my $conffile = PVE
::QemuServer
::config_file
($newid);
1964 die "unable to create VM $newid: config file already exists\n"
1967 my $newconf = { lock => 'clone' };
1971 foreach my $opt (keys %$oldconf) {
1972 my $value = $oldconf->{$opt};
1974 # do not copy snapshot related info
1975 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
1976 $opt eq 'vmstate' || $opt eq 'snapstate';
1978 # always change MAC! address
1979 if ($opt =~ m/^net(\d+)$/) {
1980 my $net = PVE
::QemuServer
::parse_net
($value);
1981 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
1982 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
1983 } elsif (my $drive = PVE
::QemuServer
::parse_drive
($opt, $value)) {
1984 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
1985 $newconf->{$opt} = $value; # simply copy configuration
1987 if ($param->{full
} || !PVE
::Storage
::volume_is_base
($storecfg, $drive->{file
})) {
1988 die "Full clone feature is not available"
1989 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
1992 $drives->{$opt} = $drive;
1993 push @$vollist, $drive->{file
};
1996 # copy everything else
1997 $newconf->{$opt} = $value;
2001 delete $newconf->{template
};
2003 if ($param->{name
}) {
2004 $newconf->{name
} = $param->{name
};
2006 if ($oldconf->{name
}) {
2007 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2009 $newconf->{name
} = "Copy-of-VM-$vmid";
2013 if ($param->{description
}) {
2014 $newconf->{description
} = $param->{description
};
2017 # create empty/temp config - this fails if VM already exists on other node
2018 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2023 my $newvollist = [];
2026 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2028 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2030 foreach my $opt (keys %$drives) {
2031 my $drive = $drives->{$opt};
2033 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2034 $newid, $storage, $format, $drive->{full
}, $newvollist);
2036 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2038 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2041 delete $newconf->{lock};
2042 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2045 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2046 die "Failed to move config to node '$target' - rename failed: $!\n"
2047 if !rename($conffile, $newconffile);
2050 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2055 sleep 1; # some storage like rbd need to wait before release volume - really?
2057 foreach my $volid (@$newvollist) {
2058 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2061 die "clone failed: $err";
2067 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2070 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2071 # Aquire exclusive lock lock for $newid
2072 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2077 __PACKAGE__-
>register_method({
2078 name
=> 'move_vm_disk',
2079 path
=> '{vmid}/move_disk',
2083 description
=> "Move volume to different storage.",
2085 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2086 "and 'Datastore.AllocateSpace' permissions on the storage.",
2089 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2090 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2094 additionalProperties
=> 0,
2096 node
=> get_standard_option
('pve-node'),
2097 vmid
=> get_standard_option
('pve-vmid'),
2100 description
=> "The disk you want to move.",
2101 enum
=> [ PVE
::QemuServer
::disknames
() ],
2103 storage
=> get_standard_option
('pve-storage-id', { description
=> "Target Storage." }),
2106 description
=> "Target Format.",
2107 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2112 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2118 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2126 description
=> "the task ID.",
2131 my $rpcenv = PVE
::RPCEnvironment
::get
();
2133 my $authuser = $rpcenv->get_user();
2135 my $node = extract_param
($param, 'node');
2137 my $vmid = extract_param
($param, 'vmid');
2139 my $digest = extract_param
($param, 'digest');
2141 my $disk = extract_param
($param, 'disk');
2143 my $storeid = extract_param
($param, 'storage');
2145 my $format = extract_param
($param, 'format');
2147 my $storecfg = PVE
::Storage
::config
();
2149 my $updatefn = sub {
2151 my $conf = PVE
::QemuServer
::load_config
($vmid);
2153 die "checksum missmatch (file change by other user?)\n"
2154 if $digest && $digest ne $conf->{digest
};
2156 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2158 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2160 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2162 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2165 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2166 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2170 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2171 (!$format || !$oldfmt || $oldfmt eq $format);
2173 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2175 my $running = PVE
::QemuServer
::check_running
($vmid);
2177 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2181 my $newvollist = [];
2184 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2186 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2187 $vmid, $storeid, $format, 1, $newvollist);
2189 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2191 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2193 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2197 foreach my $volid (@$newvollist) {
2198 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2201 die "storage migration failed: $err";
2204 if ($param->{delete}) {
2205 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2210 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2213 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2216 __PACKAGE__-
>register_method({
2217 name
=> 'migrate_vm',
2218 path
=> '{vmid}/migrate',
2222 description
=> "Migrate virtual machine. Creates a new migration task.",
2224 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2227 additionalProperties
=> 0,
2229 node
=> get_standard_option
('pve-node'),
2230 vmid
=> get_standard_option
('pve-vmid'),
2231 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2234 description
=> "Use online/live migration.",
2239 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2246 description
=> "the task ID.",
2251 my $rpcenv = PVE
::RPCEnvironment
::get
();
2253 my $authuser = $rpcenv->get_user();
2255 my $target = extract_param
($param, 'target');
2257 my $localnode = PVE
::INotify
::nodename
();
2258 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2260 PVE
::Cluster
::check_cfs_quorum
();
2262 PVE
::Cluster
::check_node_exists
($target);
2264 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2266 my $vmid = extract_param
($param, 'vmid');
2268 raise_param_exc
({ force
=> "Only root may use this option." })
2269 if $param->{force
} && $authuser ne 'root@pam';
2272 my $conf = PVE
::QemuServer
::load_config
($vmid);
2274 # try to detect errors early
2276 PVE
::QemuServer
::check_lock
($conf);
2278 if (PVE
::QemuServer
::check_running
($vmid)) {
2279 die "cant migrate running VM without --online\n"
2280 if !$param->{online
};
2283 my $storecfg = PVE
::Storage
::config
();
2284 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2286 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
2291 my $service = "pvevm:$vmid";
2293 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2295 print "Executing HA migrate for VM $vmid to node $target\n";
2297 PVE
::Tools
::run_command
($cmd);
2302 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2309 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2312 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2317 __PACKAGE__-
>register_method({
2319 path
=> '{vmid}/monitor',
2323 description
=> "Execute Qemu monitor commands.",
2325 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2328 additionalProperties
=> 0,
2330 node
=> get_standard_option
('pve-node'),
2331 vmid
=> get_standard_option
('pve-vmid'),
2334 description
=> "The monitor command.",
2338 returns
=> { type
=> 'string'},
2342 my $vmid = $param->{vmid
};
2344 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2348 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2350 $res = "ERROR: $@" if $@;
2355 __PACKAGE__-
>register_method({
2356 name
=> 'resize_vm',
2357 path
=> '{vmid}/resize',
2361 description
=> "Extend volume size.",
2363 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2366 additionalProperties
=> 0,
2368 node
=> get_standard_option
('pve-node'),
2369 vmid
=> get_standard_option
('pve-vmid'),
2370 skiplock
=> get_standard_option
('skiplock'),
2373 description
=> "The disk you want to resize.",
2374 enum
=> [PVE
::QemuServer
::disknames
()],
2378 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2379 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.",
2383 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2389 returns
=> { type
=> 'null'},
2393 my $rpcenv = PVE
::RPCEnvironment
::get
();
2395 my $authuser = $rpcenv->get_user();
2397 my $node = extract_param
($param, 'node');
2399 my $vmid = extract_param
($param, 'vmid');
2401 my $digest = extract_param
($param, 'digest');
2403 my $disk = extract_param
($param, 'disk');
2405 my $sizestr = extract_param
($param, 'size');
2407 my $skiplock = extract_param
($param, 'skiplock');
2408 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2409 if $skiplock && $authuser ne 'root@pam';
2411 my $storecfg = PVE
::Storage
::config
();
2413 my $updatefn = sub {
2415 my $conf = PVE
::QemuServer
::load_config
($vmid);
2417 die "checksum missmatch (file change by other user?)\n"
2418 if $digest && $digest ne $conf->{digest
};
2419 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2421 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2423 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2425 my $volid = $drive->{file
};
2427 die "disk '$disk' has no associated volume\n" if !$volid;
2429 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2431 die "you can't online resize a virtio windows bootdisk\n"
2432 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
2434 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2436 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2438 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2440 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2441 my ($ext, $newsize, $unit) = ($1, $2, $4);
2444 $newsize = $newsize * 1024;
2445 } elsif ($unit eq 'M') {
2446 $newsize = $newsize * 1024 * 1024;
2447 } elsif ($unit eq 'G') {
2448 $newsize = $newsize * 1024 * 1024 * 1024;
2449 } elsif ($unit eq 'T') {
2450 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2453 $newsize += $size if $ext;
2454 $newsize = int($newsize);
2456 die "unable to skrink disk size\n" if $newsize < $size;
2458 return if $size == $newsize;
2460 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2462 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2464 $drive->{size
} = $newsize;
2465 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2467 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2470 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2474 __PACKAGE__-
>register_method({
2475 name
=> 'snapshot_list',
2476 path
=> '{vmid}/snapshot',
2478 description
=> "List all snapshots.",
2480 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2483 protected
=> 1, # qemu pid files are only readable by root
2485 additionalProperties
=> 0,
2487 vmid
=> get_standard_option
('pve-vmid'),
2488 node
=> get_standard_option
('pve-node'),
2497 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2502 my $vmid = $param->{vmid
};
2504 my $conf = PVE
::QemuServer
::load_config
($vmid);
2505 my $snaphash = $conf->{snapshots
} || {};
2509 foreach my $name (keys %$snaphash) {
2510 my $d = $snaphash->{$name};
2513 snaptime
=> $d->{snaptime
} || 0,
2514 vmstate
=> $d->{vmstate
} ?
1 : 0,
2515 description
=> $d->{description
} || '',
2517 $item->{parent
} = $d->{parent
} if $d->{parent
};
2518 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2522 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2523 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2524 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2526 push @$res, $current;
2531 __PACKAGE__-
>register_method({
2533 path
=> '{vmid}/snapshot',
2537 description
=> "Snapshot a VM.",
2539 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2542 additionalProperties
=> 0,
2544 node
=> get_standard_option
('pve-node'),
2545 vmid
=> get_standard_option
('pve-vmid'),
2546 snapname
=> get_standard_option
('pve-snapshot-name'),
2550 description
=> "Save the vmstate",
2555 description
=> "Freeze the filesystem",
2560 description
=> "A textual description or comment.",
2566 description
=> "the task ID.",
2571 my $rpcenv = PVE
::RPCEnvironment
::get
();
2573 my $authuser = $rpcenv->get_user();
2575 my $node = extract_param
($param, 'node');
2577 my $vmid = extract_param
($param, 'vmid');
2579 my $snapname = extract_param
($param, 'snapname');
2581 die "unable to use snapshot name 'current' (reserved name)\n"
2582 if $snapname eq 'current';
2585 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2586 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2587 $param->{freezefs
}, $param->{description
});
2590 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2593 __PACKAGE__-
>register_method({
2594 name
=> 'snapshot_cmd_idx',
2595 path
=> '{vmid}/snapshot/{snapname}',
2602 additionalProperties
=> 0,
2604 vmid
=> get_standard_option
('pve-vmid'),
2605 node
=> get_standard_option
('pve-node'),
2606 snapname
=> get_standard_option
('pve-snapshot-name'),
2615 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2622 push @$res, { cmd
=> 'rollback' };
2623 push @$res, { cmd
=> 'config' };
2628 __PACKAGE__-
>register_method({
2629 name
=> 'update_snapshot_config',
2630 path
=> '{vmid}/snapshot/{snapname}/config',
2634 description
=> "Update snapshot metadata.",
2636 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2639 additionalProperties
=> 0,
2641 node
=> get_standard_option
('pve-node'),
2642 vmid
=> get_standard_option
('pve-vmid'),
2643 snapname
=> get_standard_option
('pve-snapshot-name'),
2647 description
=> "A textual description or comment.",
2651 returns
=> { type
=> 'null' },
2655 my $rpcenv = PVE
::RPCEnvironment
::get
();
2657 my $authuser = $rpcenv->get_user();
2659 my $vmid = extract_param
($param, 'vmid');
2661 my $snapname = extract_param
($param, 'snapname');
2663 return undef if !defined($param->{description
});
2665 my $updatefn = sub {
2667 my $conf = PVE
::QemuServer
::load_config
($vmid);
2669 PVE
::QemuServer
::check_lock
($conf);
2671 my $snap = $conf->{snapshots
}->{$snapname};
2673 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2675 $snap->{description
} = $param->{description
} if defined($param->{description
});
2677 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2680 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2685 __PACKAGE__-
>register_method({
2686 name
=> 'get_snapshot_config',
2687 path
=> '{vmid}/snapshot/{snapname}/config',
2690 description
=> "Get snapshot configuration",
2692 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2695 additionalProperties
=> 0,
2697 node
=> get_standard_option
('pve-node'),
2698 vmid
=> get_standard_option
('pve-vmid'),
2699 snapname
=> get_standard_option
('pve-snapshot-name'),
2702 returns
=> { type
=> "object" },
2706 my $rpcenv = PVE
::RPCEnvironment
::get
();
2708 my $authuser = $rpcenv->get_user();
2710 my $vmid = extract_param
($param, 'vmid');
2712 my $snapname = extract_param
($param, 'snapname');
2714 my $conf = PVE
::QemuServer
::load_config
($vmid);
2716 my $snap = $conf->{snapshots
}->{$snapname};
2718 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2723 __PACKAGE__-
>register_method({
2725 path
=> '{vmid}/snapshot/{snapname}/rollback',
2729 description
=> "Rollback VM state to specified snapshot.",
2731 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2734 additionalProperties
=> 0,
2736 node
=> get_standard_option
('pve-node'),
2737 vmid
=> get_standard_option
('pve-vmid'),
2738 snapname
=> get_standard_option
('pve-snapshot-name'),
2743 description
=> "the task ID.",
2748 my $rpcenv = PVE
::RPCEnvironment
::get
();
2750 my $authuser = $rpcenv->get_user();
2752 my $node = extract_param
($param, 'node');
2754 my $vmid = extract_param
($param, 'vmid');
2756 my $snapname = extract_param
($param, 'snapname');
2759 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2760 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2763 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2766 __PACKAGE__-
>register_method({
2767 name
=> 'delsnapshot',
2768 path
=> '{vmid}/snapshot/{snapname}',
2772 description
=> "Delete a VM snapshot.",
2774 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2777 additionalProperties
=> 0,
2779 node
=> get_standard_option
('pve-node'),
2780 vmid
=> get_standard_option
('pve-vmid'),
2781 snapname
=> get_standard_option
('pve-snapshot-name'),
2785 description
=> "For removal from config file, even if removing disk snapshots fails.",
2791 description
=> "the task ID.",
2796 my $rpcenv = PVE
::RPCEnvironment
::get
();
2798 my $authuser = $rpcenv->get_user();
2800 my $node = extract_param
($param, 'node');
2802 my $vmid = extract_param
($param, 'vmid');
2804 my $snapname = extract_param
($param, 'snapname');
2807 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2808 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2811 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2814 __PACKAGE__-
>register_method({
2816 path
=> '{vmid}/template',
2820 description
=> "Create a Template.",
2822 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
2823 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2826 additionalProperties
=> 0,
2828 node
=> get_standard_option
('pve-node'),
2829 vmid
=> get_standard_option
('pve-vmid'),
2833 description
=> "If you want to convert only 1 disk to base image.",
2834 enum
=> [PVE
::QemuServer
::disknames
()],
2839 returns
=> { type
=> 'null'},
2843 my $rpcenv = PVE
::RPCEnvironment
::get
();
2845 my $authuser = $rpcenv->get_user();
2847 my $node = extract_param
($param, 'node');
2849 my $vmid = extract_param
($param, 'vmid');
2851 my $disk = extract_param
($param, 'disk');
2853 my $updatefn = sub {
2855 my $conf = PVE
::QemuServer
::load_config
($vmid);
2857 PVE
::QemuServer
::check_lock
($conf);
2859 die "unable to create template, because VM contains snapshots\n"
2860 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
2862 die "you can't convert a template to a template\n"
2863 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2865 die "you can't convert a VM to template if VM is running\n"
2866 if PVE
::QemuServer
::check_running
($vmid);
2869 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2872 $conf->{template
} = 1;
2873 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2875 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2878 PVE
::QemuServer
::lock_config
($vmid, $updatefn);