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);
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 # Note: $pool is only needed when creating a VM, because pool permissions
63 # are automatically inherited if VM already exists inside a pool.
64 my $create_disks = sub {
65 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
70 PVE
::QemuServer
::foreach_drive
($settings, sub {
73 my $volid = $disk->{file
};
75 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
77 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
78 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
79 my ($storeid, $size) = ($2 || $default_storage, $3);
80 die "no storage ID specified (and no default storage)\n" if !$storeid;
81 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
82 my $fmt = $disk->{format
} || $defformat;
83 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
84 $fmt, undef, $size*1024*1024);
85 $disk->{file
} = $volid;
86 $disk->{size
} = $size*1024*1024*1024;
87 push @$vollist, $volid;
88 delete $disk->{format
}; # no longer needed
89 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
92 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
94 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
96 my $foundvolid = undef;
99 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
100 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
102 PVE
::Storage
::foreach_volid
($dl, sub {
104 if($volumeid eq $volid) {
111 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
113 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
114 $disk->{size
} = $size;
115 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
119 # free allocated images on error
121 syslog
('err', "VM $vmid creating disks failed");
122 foreach my $volid (@$vollist) {
123 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
129 # modify vm config if everything went well
130 foreach my $ds (keys %$res) {
131 $conf->{$ds} = $res->{$ds};
137 my $check_vm_modify_config_perm = sub {
138 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
140 return 1 if $authuser eq 'root@pam';
142 foreach my $opt (@$key_list) {
143 # disk checks need to be done somewhere else
144 next if PVE
::QemuServer
::valid_drivename
($opt);
146 if ($opt eq 'sockets' || $opt eq 'cores' ||
147 $opt eq 'cpu' || $opt eq 'smp' ||
148 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
149 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
150 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
151 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
152 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
153 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
154 } elsif ($opt eq 'args' || $opt eq 'lock') {
155 die "only root can set '$opt' config\n";
156 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
157 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
158 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
159 } elsif ($opt =~ m/^net\d+$/) {
160 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
162 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
169 __PACKAGE__-
>register_method({
173 description
=> "Virtual machine index (per node).",
175 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
179 protected
=> 1, # qemu pid files are only readable by root
181 additionalProperties
=> 0,
183 node
=> get_standard_option
('pve-node'),
192 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
197 my $rpcenv = PVE
::RPCEnvironment
::get
();
198 my $authuser = $rpcenv->get_user();
200 my $vmstatus = PVE
::QemuServer
::vmstatus
();
203 foreach my $vmid (keys %$vmstatus) {
204 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
206 my $data = $vmstatus->{$vmid};
207 $data->{vmid
} = $vmid;
214 __PACKAGE__-
>register_method({
218 description
=> "Create or restore a virtual machine.",
220 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
222 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
223 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
229 additionalProperties
=> 0,
230 properties
=> PVE
::QemuServer
::json_config_properties
(
232 node
=> get_standard_option
('pve-node'),
233 vmid
=> get_standard_option
('pve-vmid'),
235 description
=> "The backup file.",
240 storage
=> get_standard_option
('pve-storage-id', {
241 description
=> "Default storage.",
247 description
=> "Allow to overwrite existing VM.",
248 requires
=> 'archive',
253 description
=> "Assign a unique random ethernet address.",
254 requires
=> 'archive',
258 type
=> 'string', format
=> 'pve-poolid',
259 description
=> "Add the VM to the specified pool.",
269 my $rpcenv = PVE
::RPCEnvironment
::get
();
271 my $authuser = $rpcenv->get_user();
273 my $node = extract_param
($param, 'node');
275 my $vmid = extract_param
($param, 'vmid');
277 my $archive = extract_param
($param, 'archive');
279 my $storage = extract_param
($param, 'storage');
281 my $force = extract_param
($param, 'force');
283 my $unique = extract_param
($param, 'unique');
285 my $pool = extract_param
($param, 'pool');
287 my $filename = PVE
::QemuServer
::config_file
($vmid);
289 my $storecfg = PVE
::Storage
::config
();
291 PVE
::Cluster
::check_cfs_quorum
();
293 if (defined($pool)) {
294 $rpcenv->check_pool_exist($pool);
297 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
298 if defined($storage);
301 &$resolve_cdrom_alias($param);
303 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
305 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
307 foreach my $opt (keys %$param) {
308 if (PVE
::QemuServer
::valid_drivename
($opt)) {
309 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
310 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
312 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
313 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
317 PVE
::QemuServer
::add_random_macs
($param);
319 my $keystr = join(' ', keys %$param);
320 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
322 if ($archive eq '-') {
323 die "pipe requires cli environment\n"
324 if $rpcenv->{type
} ne 'cli';
326 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
328 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
329 if PVE
::Storage
::parse_volume_id
($archive, 1);
331 die "can't find archive file '$archive'\n" if !($path && -f
$path);
336 my $addVMtoPoolFn = sub {
337 my $usercfg = cfs_read_file
("user.cfg");
338 if (my $data = $usercfg->{pools
}->{$pool}) {
339 $data->{vms
}->{$vmid} = 1;
340 $usercfg->{vms
}->{$vmid} = $pool;
341 cfs_write_file
("user.cfg", $usercfg);
345 my $restorefn = sub {
348 die "unable to restore vm $vmid: config file already exists\n"
351 die "unable to restore vm $vmid: vm is running\n"
352 if PVE
::QemuServer
::check_running
($vmid);
356 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
359 unique
=> $unique });
361 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
364 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
370 die "unable to create vm $vmid: config file already exists\n"
381 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
383 # try to be smart about bootdisk
384 my @disks = PVE
::QemuServer
::disknames
();
386 foreach my $ds (reverse @disks) {
387 next if !$conf->{$ds};
388 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
389 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
393 if (!$conf->{bootdisk
} && $firstdisk) {
394 $conf->{bootdisk
} = $firstdisk;
397 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
403 foreach my $volid (@$vollist) {
404 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
407 die "create failed - $err";
410 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
413 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
416 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
419 __PACKAGE__-
>register_method({
424 description
=> "Directory index",
429 additionalProperties
=> 0,
431 node
=> get_standard_option
('pve-node'),
432 vmid
=> get_standard_option
('pve-vmid'),
440 subdir
=> { type
=> 'string' },
443 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
449 { subdir
=> 'config' },
450 { subdir
=> 'status' },
451 { subdir
=> 'unlink' },
452 { subdir
=> 'vncproxy' },
453 { subdir
=> 'migrate' },
454 { subdir
=> 'resize' },
456 { subdir
=> 'rrddata' },
457 { subdir
=> 'monitor' },
458 { subdir
=> 'snapshot' },
464 __PACKAGE__-
>register_method({
466 path
=> '{vmid}/rrd',
468 protected
=> 1, # fixme: can we avoid that?
470 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
472 description
=> "Read VM RRD statistics (returns PNG)",
474 additionalProperties
=> 0,
476 node
=> get_standard_option
('pve-node'),
477 vmid
=> get_standard_option
('pve-vmid'),
479 description
=> "Specify the time frame you are interested in.",
481 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
484 description
=> "The list of datasources you want to display.",
485 type
=> 'string', format
=> 'pve-configid-list',
488 description
=> "The RRD consolidation function",
490 enum
=> [ 'AVERAGE', 'MAX' ],
498 filename
=> { type
=> 'string' },
504 return PVE
::Cluster
::create_rrd_graph
(
505 "pve2-vm/$param->{vmid}", $param->{timeframe
},
506 $param->{ds
}, $param->{cf
});
510 __PACKAGE__-
>register_method({
512 path
=> '{vmid}/rrddata',
514 protected
=> 1, # fixme: can we avoid that?
516 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
518 description
=> "Read VM RRD statistics",
520 additionalProperties
=> 0,
522 node
=> get_standard_option
('pve-node'),
523 vmid
=> get_standard_option
('pve-vmid'),
525 description
=> "Specify the time frame you are interested in.",
527 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
530 description
=> "The RRD consolidation function",
532 enum
=> [ 'AVERAGE', 'MAX' ],
547 return PVE
::Cluster
::create_rrd_data
(
548 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
552 __PACKAGE__-
>register_method({
554 path
=> '{vmid}/config',
557 description
=> "Get virtual machine configuration.",
559 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
562 additionalProperties
=> 0,
564 node
=> get_standard_option
('pve-node'),
565 vmid
=> get_standard_option
('pve-vmid'),
573 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
580 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
582 delete $conf->{snapshots
};
587 my $vm_is_volid_owner = sub {
588 my ($storecfg, $vmid, $volid) =@_;
590 if ($volid !~ m
|^/|) {
592 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
593 if ($owner && ($owner == $vmid)) {
601 my $test_deallocate_drive = sub {
602 my ($storecfg, $vmid, $key, $drive, $force) = @_;
604 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
605 my $volid = $drive->{file
};
606 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
607 if ($force || $key =~ m/^unused/) {
608 my $sid = PVE
::Storage
::parse_volume_id
($volid);
617 my $delete_drive = sub {
618 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
620 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
621 my $volid = $drive->{file
};
622 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
623 if ($force || $key =~ m/^unused/) {
624 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
627 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
632 delete $conf->{$key};
635 my $vmconfig_delete_option = sub {
636 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
638 return if !defined($conf->{$opt});
640 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
643 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
645 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
646 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
647 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
651 my $unplugwarning = "";
652 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
653 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
654 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
655 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
656 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
657 $unplugwarning = "<br>verify that your guest support acpi hotplug";
660 if($opt eq 'tablet'){
661 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
663 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
667 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
668 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
670 delete $conf->{$opt};
673 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
676 my $safe_num_ne = sub {
679 return 0 if !defined($a) && !defined($b);
680 return 1 if !defined($a);
681 return 1 if !defined($b);
686 my $vmconfig_update_disk = sub {
687 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
689 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
691 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
692 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
694 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
699 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
701 my $media = $drive->{media
} || 'disk';
702 my $oldmedia = $old_drive->{media
} || 'disk';
703 die "unable to change media type\n" if $media ne $oldmedia;
705 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
706 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
708 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
709 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
712 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
713 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
714 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
715 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
716 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
717 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
718 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
719 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
720 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
721 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
726 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
727 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
729 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
730 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
732 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
734 if (PVE
::QemuServer
::check_running
($vmid)) {
735 if ($drive->{file
} eq 'none') {
736 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
738 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
739 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
740 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
744 } else { # hotplug new disks
746 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
750 my $vmconfig_update_net = sub {
751 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
753 if ($conf->{$opt} && PVE
::QemuServer
::check_running
($vmid)) {
754 my $oldnet = PVE
::QemuServer
::parse_net
($conf->{$opt});
755 my $newnet = PVE
::QemuServer
::parse_net
($value);
757 if($oldnet->{model
} ne $newnet->{model
}){
758 #if model change, we try to hot-unplug
759 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
762 if($newnet->{bridge
} && $oldnet->{bridge
}){
763 my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
765 if($newnet->{rate
} ne $oldnet->{rate
}){
766 PVE
::Network
::tap_rate_limit
($iface, $newnet->{rate
});
769 if(($newnet->{bridge
} ne $oldnet->{bridge
}) || ($newnet->{tag
} ne $oldnet->{tag
})){
770 eval{PVE
::Network
::tap_unplug
($iface, $oldnet->{bridge
}, $oldnet->{tag
});};
771 PVE
::Network
::tap_plug
($iface, $newnet->{bridge
}, $newnet->{tag
});
775 #if bridge/nat mode change, we try to hot-unplug
776 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
781 $conf->{$opt} = $value;
782 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
783 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
785 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
787 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
790 my $vm_config_perm_list = [
800 __PACKAGE__-
>register_method({
802 path
=> '{vmid}/config',
806 description
=> "Set virtual machine options.",
808 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
811 additionalProperties
=> 0,
812 properties
=> PVE
::QemuServer
::json_config_properties
(
814 node
=> get_standard_option
('pve-node'),
815 vmid
=> get_standard_option
('pve-vmid'),
816 skiplock
=> get_standard_option
('skiplock'),
818 type
=> 'string', format
=> 'pve-configid-list',
819 description
=> "A list of settings you want to delete.",
824 description
=> $opt_force_description,
826 requires
=> 'delete',
830 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
836 returns
=> { type
=> 'null'},
840 my $rpcenv = PVE
::RPCEnvironment
::get
();
842 my $authuser = $rpcenv->get_user();
844 my $node = extract_param
($param, 'node');
846 my $vmid = extract_param
($param, 'vmid');
848 my $digest = extract_param
($param, 'digest');
850 my @paramarr = (); # used for log message
851 foreach my $key (keys %$param) {
852 push @paramarr, "-$key", $param->{$key};
855 my $skiplock = extract_param
($param, 'skiplock');
856 raise_param_exc
({ skiplock
=> "Only root may use this option." })
857 if $skiplock && $authuser ne 'root@pam';
859 my $delete_str = extract_param
($param, 'delete');
861 my $force = extract_param
($param, 'force');
863 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
865 my $storecfg = PVE
::Storage
::config
();
867 my $defaults = PVE
::QemuServer
::load_defaults
();
869 &$resolve_cdrom_alias($param);
871 # now try to verify all parameters
874 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
875 $opt = 'ide2' if $opt eq 'cdrom';
876 raise_param_exc
({ delete => "you can't use '-$opt' and " .
877 "-delete $opt' at the same time" })
878 if defined($param->{$opt});
880 if (!PVE
::QemuServer
::option_exists
($opt)) {
881 raise_param_exc
({ delete => "unknown option '$opt'" });
887 foreach my $opt (keys %$param) {
888 if (PVE
::QemuServer
::valid_drivename
($opt)) {
890 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
891 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
892 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
893 } elsif ($opt =~ m/^net(\d+)$/) {
895 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
896 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
900 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
902 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
904 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
908 my $conf = PVE
::QemuServer
::load_config
($vmid);
910 die "checksum missmatch (file change by other user?)\n"
911 if $digest && $digest ne $conf->{digest
};
913 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
915 if ($param->{memory
} || defined($param->{balloon
})) {
916 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
917 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
919 die "balloon value too large (must be smaller than assigned memory)\n"
920 if $balloon > $maxmem;
923 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
925 foreach my $opt (@delete) { # delete
926 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
927 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
930 my $running = PVE
::QemuServer
::check_running
($vmid);
932 foreach my $opt (keys %$param) { # add/change
934 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
936 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
938 if (PVE
::QemuServer
::valid_drivename
($opt)) {
940 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
941 $opt, $param->{$opt}, $force);
943 } elsif ($opt =~ m/^net(\d+)$/) { #nics
945 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
946 $opt, $param->{$opt});
950 if($opt eq 'tablet' && $param->{$opt} == 1){
951 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
952 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
953 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
956 $conf->{$opt} = $param->{$opt};
957 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
961 # allow manual ballooning if shares is set to zero
962 if ($running && defined($param->{balloon
}) &&
963 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
964 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
965 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
970 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
976 __PACKAGE__-
>register_method({
977 name
=> 'destroy_vm',
982 description
=> "Destroy the vm (also delete all used/owned volumes).",
984 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
987 additionalProperties
=> 0,
989 node
=> get_standard_option
('pve-node'),
990 vmid
=> get_standard_option
('pve-vmid'),
991 skiplock
=> get_standard_option
('skiplock'),
1000 my $rpcenv = PVE
::RPCEnvironment
::get
();
1002 my $authuser = $rpcenv->get_user();
1004 my $vmid = $param->{vmid
};
1006 my $skiplock = $param->{skiplock
};
1007 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1008 if $skiplock && $authuser ne 'root@pam';
1011 my $conf = PVE
::QemuServer
::load_config
($vmid);
1013 my $storecfg = PVE
::Storage
::config
();
1015 my $delVMfromPoolFn = sub {
1016 my $usercfg = cfs_read_file
("user.cfg");
1017 if (my $pool = $usercfg->{vms
}->{$vmid}) {
1018 if (my $data = $usercfg->{pools
}->{$pool}) {
1019 delete $data->{vms
}->{$vmid};
1020 delete $usercfg->{vms
}->{$vmid};
1021 cfs_write_file
("user.cfg", $usercfg);
1029 syslog
('info', "destroy VM $vmid: $upid\n");
1031 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1033 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
1036 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1039 __PACKAGE__-
>register_method({
1041 path
=> '{vmid}/unlink',
1045 description
=> "Unlink/delete disk images.",
1047 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1050 additionalProperties
=> 0,
1052 node
=> get_standard_option
('pve-node'),
1053 vmid
=> get_standard_option
('pve-vmid'),
1055 type
=> 'string', format
=> 'pve-configid-list',
1056 description
=> "A list of disk IDs you want to delete.",
1060 description
=> $opt_force_description,
1065 returns
=> { type
=> 'null'},
1069 $param->{delete} = extract_param
($param, 'idlist');
1071 __PACKAGE__-
>update_vm($param);
1078 __PACKAGE__-
>register_method({
1080 path
=> '{vmid}/vncproxy',
1084 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1086 description
=> "Creates a TCP VNC proxy connections.",
1088 additionalProperties
=> 0,
1090 node
=> get_standard_option
('pve-node'),
1091 vmid
=> get_standard_option
('pve-vmid'),
1095 additionalProperties
=> 0,
1097 user
=> { type
=> 'string' },
1098 ticket
=> { type
=> 'string' },
1099 cert
=> { type
=> 'string' },
1100 port
=> { type
=> 'integer' },
1101 upid
=> { type
=> 'string' },
1107 my $rpcenv = PVE
::RPCEnvironment
::get
();
1109 my $authuser = $rpcenv->get_user();
1111 my $vmid = $param->{vmid
};
1112 my $node = $param->{node
};
1114 my $authpath = "/vms/$vmid";
1116 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1118 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1121 my $port = PVE
::Tools
::next_vnc_port
();
1125 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1126 $remip = PVE
::Cluster
::remote_node_ip
($node);
1129 # NOTE: kvm VNC traffic is already TLS encrypted
1130 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1137 syslog
('info', "starting vnc proxy $upid\n");
1139 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1141 my $qmstr = join(' ', @$qmcmd);
1143 # also redirect stderr (else we get RFB protocol errors)
1144 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1146 PVE
::Tools
::run_command
($cmd);
1151 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1153 PVE
::Tools
::wait_for_vnc_port
($port);
1164 __PACKAGE__-
>register_method({
1166 path
=> '{vmid}/status',
1169 description
=> "Directory index",
1174 additionalProperties
=> 0,
1176 node
=> get_standard_option
('pve-node'),
1177 vmid
=> get_standard_option
('pve-vmid'),
1185 subdir
=> { type
=> 'string' },
1188 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1194 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1197 { subdir
=> 'current' },
1198 { subdir
=> 'start' },
1199 { subdir
=> 'stop' },
1205 my $vm_is_ha_managed = sub {
1208 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1209 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1215 __PACKAGE__-
>register_method({
1216 name
=> 'vm_status',
1217 path
=> '{vmid}/status/current',
1220 protected
=> 1, # qemu pid files are only readable by root
1221 description
=> "Get virtual machine status.",
1223 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1226 additionalProperties
=> 0,
1228 node
=> get_standard_option
('pve-node'),
1229 vmid
=> get_standard_option
('pve-vmid'),
1232 returns
=> { type
=> 'object' },
1237 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1239 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1240 my $status = $vmstatus->{$param->{vmid
}};
1242 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1247 __PACKAGE__-
>register_method({
1249 path
=> '{vmid}/status/start',
1253 description
=> "Start virtual machine.",
1255 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1258 additionalProperties
=> 0,
1260 node
=> get_standard_option
('pve-node'),
1261 vmid
=> get_standard_option
('pve-vmid'),
1262 skiplock
=> get_standard_option
('skiplock'),
1263 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1264 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1274 my $rpcenv = PVE
::RPCEnvironment
::get
();
1276 my $authuser = $rpcenv->get_user();
1278 my $node = extract_param
($param, 'node');
1280 my $vmid = extract_param
($param, 'vmid');
1282 my $stateuri = extract_param
($param, 'stateuri');
1283 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1284 if $stateuri && $authuser ne 'root@pam';
1286 my $skiplock = extract_param
($param, 'skiplock');
1287 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1288 if $skiplock && $authuser ne 'root@pam';
1290 my $migratedfrom = extract_param
($param, 'migratedfrom');
1291 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1292 if $migratedfrom && $authuser ne 'root@pam';
1294 my $storecfg = PVE
::Storage
::config
();
1296 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1297 $rpcenv->{type
} ne 'ha') {
1302 my $service = "pvevm:$vmid";
1304 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1306 print "Executing HA start for VM $vmid\n";
1308 PVE
::Tools
::run_command
($cmd);
1313 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1320 syslog
('info', "start VM $vmid: $upid\n");
1322 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1327 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1331 __PACKAGE__-
>register_method({
1333 path
=> '{vmid}/status/stop',
1337 description
=> "Stop virtual machine.",
1339 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1342 additionalProperties
=> 0,
1344 node
=> get_standard_option
('pve-node'),
1345 vmid
=> get_standard_option
('pve-vmid'),
1346 skiplock
=> get_standard_option
('skiplock'),
1347 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1349 description
=> "Wait maximal timeout seconds.",
1355 description
=> "Do not decativate storage volumes.",
1368 my $rpcenv = PVE
::RPCEnvironment
::get
();
1370 my $authuser = $rpcenv->get_user();
1372 my $node = extract_param
($param, 'node');
1374 my $vmid = extract_param
($param, 'vmid');
1376 my $skiplock = extract_param
($param, 'skiplock');
1377 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1378 if $skiplock && $authuser ne 'root@pam';
1380 my $keepActive = extract_param
($param, 'keepActive');
1381 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1382 if $keepActive && $authuser ne 'root@pam';
1384 my $migratedfrom = extract_param
($param, 'migratedfrom');
1385 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1386 if $migratedfrom && $authuser ne 'root@pam';
1389 my $storecfg = PVE
::Storage
::config
();
1391 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1396 my $service = "pvevm:$vmid";
1398 my $cmd = ['clusvcadm', '-d', $service];
1400 print "Executing HA stop for VM $vmid\n";
1402 PVE
::Tools
::run_command
($cmd);
1407 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1413 syslog
('info', "stop VM $vmid: $upid\n");
1415 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1416 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1421 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1425 __PACKAGE__-
>register_method({
1427 path
=> '{vmid}/status/reset',
1431 description
=> "Reset virtual machine.",
1433 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1436 additionalProperties
=> 0,
1438 node
=> get_standard_option
('pve-node'),
1439 vmid
=> get_standard_option
('pve-vmid'),
1440 skiplock
=> get_standard_option
('skiplock'),
1449 my $rpcenv = PVE
::RPCEnvironment
::get
();
1451 my $authuser = $rpcenv->get_user();
1453 my $node = extract_param
($param, 'node');
1455 my $vmid = extract_param
($param, 'vmid');
1457 my $skiplock = extract_param
($param, 'skiplock');
1458 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1459 if $skiplock && $authuser ne 'root@pam';
1461 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1466 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1471 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1474 __PACKAGE__-
>register_method({
1475 name
=> 'vm_shutdown',
1476 path
=> '{vmid}/status/shutdown',
1480 description
=> "Shutdown virtual machine.",
1482 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1485 additionalProperties
=> 0,
1487 node
=> get_standard_option
('pve-node'),
1488 vmid
=> get_standard_option
('pve-vmid'),
1489 skiplock
=> get_standard_option
('skiplock'),
1491 description
=> "Wait maximal timeout seconds.",
1497 description
=> "Make sure the VM stops.",
1503 description
=> "Do not decativate storage volumes.",
1516 my $rpcenv = PVE
::RPCEnvironment
::get
();
1518 my $authuser = $rpcenv->get_user();
1520 my $node = extract_param
($param, 'node');
1522 my $vmid = extract_param
($param, 'vmid');
1524 my $skiplock = extract_param
($param, 'skiplock');
1525 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1526 if $skiplock && $authuser ne 'root@pam';
1528 my $keepActive = extract_param
($param, 'keepActive');
1529 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1530 if $keepActive && $authuser ne 'root@pam';
1532 my $storecfg = PVE
::Storage
::config
();
1537 syslog
('info', "shutdown VM $vmid: $upid\n");
1539 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1540 1, $param->{forceStop
}, $keepActive);
1545 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1548 __PACKAGE__-
>register_method({
1549 name
=> 'vm_suspend',
1550 path
=> '{vmid}/status/suspend',
1554 description
=> "Suspend virtual machine.",
1556 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1559 additionalProperties
=> 0,
1561 node
=> get_standard_option
('pve-node'),
1562 vmid
=> get_standard_option
('pve-vmid'),
1563 skiplock
=> get_standard_option
('skiplock'),
1572 my $rpcenv = PVE
::RPCEnvironment
::get
();
1574 my $authuser = $rpcenv->get_user();
1576 my $node = extract_param
($param, 'node');
1578 my $vmid = extract_param
($param, 'vmid');
1580 my $skiplock = extract_param
($param, 'skiplock');
1581 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1582 if $skiplock && $authuser ne 'root@pam';
1584 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1589 syslog
('info', "suspend VM $vmid: $upid\n");
1591 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1596 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1599 __PACKAGE__-
>register_method({
1600 name
=> 'vm_resume',
1601 path
=> '{vmid}/status/resume',
1605 description
=> "Resume virtual machine.",
1607 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1610 additionalProperties
=> 0,
1612 node
=> get_standard_option
('pve-node'),
1613 vmid
=> get_standard_option
('pve-vmid'),
1614 skiplock
=> get_standard_option
('skiplock'),
1623 my $rpcenv = PVE
::RPCEnvironment
::get
();
1625 my $authuser = $rpcenv->get_user();
1627 my $node = extract_param
($param, 'node');
1629 my $vmid = extract_param
($param, 'vmid');
1631 my $skiplock = extract_param
($param, 'skiplock');
1632 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1633 if $skiplock && $authuser ne 'root@pam';
1635 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1640 syslog
('info', "resume VM $vmid: $upid\n");
1642 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1647 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1650 __PACKAGE__-
>register_method({
1651 name
=> 'vm_sendkey',
1652 path
=> '{vmid}/sendkey',
1656 description
=> "Send key event to virtual machine.",
1658 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1661 additionalProperties
=> 0,
1663 node
=> get_standard_option
('pve-node'),
1664 vmid
=> get_standard_option
('pve-vmid'),
1665 skiplock
=> get_standard_option
('skiplock'),
1667 description
=> "The key (qemu monitor encoding).",
1672 returns
=> { type
=> 'null'},
1676 my $rpcenv = PVE
::RPCEnvironment
::get
();
1678 my $authuser = $rpcenv->get_user();
1680 my $node = extract_param
($param, 'node');
1682 my $vmid = extract_param
($param, 'vmid');
1684 my $skiplock = extract_param
($param, 'skiplock');
1685 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1686 if $skiplock && $authuser ne 'root@pam';
1688 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1693 __PACKAGE__-
>register_method({
1694 name
=> 'vm_feature',
1695 path
=> '{vmid}/feature',
1699 description
=> "Check if feature for virtual machine is available.",
1701 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1704 additionalProperties
=> 0,
1706 node
=> get_standard_option
('pve-node'),
1707 vmid
=> get_standard_option
('pve-vmid'),
1709 description
=> "Feature to check.",
1711 enum
=> [ 'snapshot', 'clone' ],
1713 snapname
=> get_standard_option
('pve-snapshot-name', {
1725 my $node = extract_param
($param, 'node');
1727 my $vmid = extract_param
($param, 'vmid');
1729 my $snapname = extract_param
($param, 'snapname');
1731 my $feature = extract_param
($param, 'feature');
1733 my $running = PVE
::QemuServer
::check_running
($vmid);
1735 my $conf = PVE
::QemuServer
::load_config
($vmid);
1738 my $snap = $conf->{snapshots
}->{$snapname};
1739 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1742 my $storecfg = PVE
::Storage
::config
();
1744 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1745 my $res = $hasfeature ?
1 : 0 ;
1749 __PACKAGE__-
>register_method({
1750 name
=> 'migrate_vm',
1751 path
=> '{vmid}/migrate',
1755 description
=> "Migrate virtual machine. Creates a new migration task.",
1757 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1760 additionalProperties
=> 0,
1762 node
=> get_standard_option
('pve-node'),
1763 vmid
=> get_standard_option
('pve-vmid'),
1764 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1767 description
=> "Use online/live migration.",
1772 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1779 description
=> "the task ID.",
1784 my $rpcenv = PVE
::RPCEnvironment
::get
();
1786 my $authuser = $rpcenv->get_user();
1788 my $target = extract_param
($param, 'target');
1790 my $localnode = PVE
::INotify
::nodename
();
1791 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1793 PVE
::Cluster
::check_cfs_quorum
();
1795 PVE
::Cluster
::check_node_exists
($target);
1797 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1799 my $vmid = extract_param
($param, 'vmid');
1801 raise_param_exc
({ force
=> "Only root may use this option." })
1802 if $param->{force
} && $authuser ne 'root@pam';
1805 my $conf = PVE
::QemuServer
::load_config
($vmid);
1807 # try to detect errors early
1809 PVE
::QemuServer
::check_lock
($conf);
1811 if (PVE
::QemuServer
::check_running
($vmid)) {
1812 die "cant migrate running VM without --online\n"
1813 if !$param->{online
};
1816 my $storecfg = PVE
::Storage
::config
();
1817 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1819 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1824 my $service = "pvevm:$vmid";
1826 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1828 print "Executing HA migrate for VM $vmid to node $target\n";
1830 PVE
::Tools
::run_command
($cmd);
1835 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1842 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1845 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1850 __PACKAGE__-
>register_method({
1852 path
=> '{vmid}/monitor',
1856 description
=> "Execute Qemu monitor commands.",
1858 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1861 additionalProperties
=> 0,
1863 node
=> get_standard_option
('pve-node'),
1864 vmid
=> get_standard_option
('pve-vmid'),
1867 description
=> "The monitor command.",
1871 returns
=> { type
=> 'string'},
1875 my $vmid = $param->{vmid
};
1877 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1881 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1883 $res = "ERROR: $@" if $@;
1888 __PACKAGE__-
>register_method({
1889 name
=> 'resize_vm',
1890 path
=> '{vmid}/resize',
1894 description
=> "Extend volume size.",
1896 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1899 additionalProperties
=> 0,
1901 node
=> get_standard_option
('pve-node'),
1902 vmid
=> get_standard_option
('pve-vmid'),
1903 skiplock
=> get_standard_option
('skiplock'),
1906 description
=> "The disk you want to resize.",
1907 enum
=> [PVE
::QemuServer
::disknames
()],
1911 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1912 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.",
1916 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1922 returns
=> { type
=> 'null'},
1926 my $rpcenv = PVE
::RPCEnvironment
::get
();
1928 my $authuser = $rpcenv->get_user();
1930 my $node = extract_param
($param, 'node');
1932 my $vmid = extract_param
($param, 'vmid');
1934 my $digest = extract_param
($param, 'digest');
1936 my $disk = extract_param
($param, 'disk');
1938 my $sizestr = extract_param
($param, 'size');
1940 my $skiplock = extract_param
($param, 'skiplock');
1941 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1942 if $skiplock && $authuser ne 'root@pam';
1944 my $storecfg = PVE
::Storage
::config
();
1946 my $updatefn = sub {
1948 my $conf = PVE
::QemuServer
::load_config
($vmid);
1950 die "checksum missmatch (file change by other user?)\n"
1951 if $digest && $digest ne $conf->{digest
};
1952 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1954 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1956 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1958 my $volid = $drive->{file
};
1960 die "disk '$disk' has no associated volume\n" if !$volid;
1962 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1964 die "you can't online resize a virtio windows bootdisk\n"
1965 if PVE
::QemuServer
::check_running
($vmid) && $conf->{bootdisk
} eq $disk && $conf->{ostype
} =~ m/^w/ && $disk =~ m/^virtio/;
1967 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1969 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1971 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1973 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1974 my ($ext, $newsize, $unit) = ($1, $2, $4);
1977 $newsize = $newsize * 1024;
1978 } elsif ($unit eq 'M') {
1979 $newsize = $newsize * 1024 * 1024;
1980 } elsif ($unit eq 'G') {
1981 $newsize = $newsize * 1024 * 1024 * 1024;
1982 } elsif ($unit eq 'T') {
1983 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1986 $newsize += $size if $ext;
1987 $newsize = int($newsize);
1989 die "unable to skrink disk size\n" if $newsize < $size;
1991 return if $size == $newsize;
1993 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1995 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1997 $drive->{size
} = $newsize;
1998 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2000 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2003 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2007 __PACKAGE__-
>register_method({
2008 name
=> 'snapshot_list',
2009 path
=> '{vmid}/snapshot',
2011 description
=> "List all snapshots.",
2013 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2016 protected
=> 1, # qemu pid files are only readable by root
2018 additionalProperties
=> 0,
2020 vmid
=> get_standard_option
('pve-vmid'),
2021 node
=> get_standard_option
('pve-node'),
2030 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2035 my $vmid = $param->{vmid
};
2037 my $conf = PVE
::QemuServer
::load_config
($vmid);
2038 my $snaphash = $conf->{snapshots
} || {};
2042 foreach my $name (keys %$snaphash) {
2043 my $d = $snaphash->{$name};
2046 snaptime
=> $d->{snaptime
} || 0,
2047 vmstate
=> $d->{vmstate
} ?
1 : 0,
2048 description
=> $d->{description
} || '',
2050 $item->{parent
} = $d->{parent
} if $d->{parent
};
2051 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2055 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2056 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2057 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2059 push @$res, $current;
2064 __PACKAGE__-
>register_method({
2066 path
=> '{vmid}/snapshot',
2070 description
=> "Snapshot a VM.",
2072 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2075 additionalProperties
=> 0,
2077 node
=> get_standard_option
('pve-node'),
2078 vmid
=> get_standard_option
('pve-vmid'),
2079 snapname
=> get_standard_option
('pve-snapshot-name'),
2083 description
=> "Save the vmstate",
2088 description
=> "Freeze the filesystem",
2093 description
=> "A textual description or comment.",
2099 description
=> "the task ID.",
2104 my $rpcenv = PVE
::RPCEnvironment
::get
();
2106 my $authuser = $rpcenv->get_user();
2108 my $node = extract_param
($param, 'node');
2110 my $vmid = extract_param
($param, 'vmid');
2112 my $snapname = extract_param
($param, 'snapname');
2114 die "unable to use snapshot name 'current' (reserved name)\n"
2115 if $snapname eq 'current';
2118 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2119 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2120 $param->{freezefs
}, $param->{description
});
2123 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2126 __PACKAGE__-
>register_method({
2127 name
=> 'snapshot_cmd_idx',
2128 path
=> '{vmid}/snapshot/{snapname}',
2135 additionalProperties
=> 0,
2137 vmid
=> get_standard_option
('pve-vmid'),
2138 node
=> get_standard_option
('pve-node'),
2139 snapname
=> get_standard_option
('pve-snapshot-name'),
2148 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2155 push @$res, { cmd
=> 'rollback' };
2156 push @$res, { cmd
=> 'config' };
2161 __PACKAGE__-
>register_method({
2162 name
=> 'update_snapshot_config',
2163 path
=> '{vmid}/snapshot/{snapname}/config',
2167 description
=> "Update snapshot metadata.",
2169 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2172 additionalProperties
=> 0,
2174 node
=> get_standard_option
('pve-node'),
2175 vmid
=> get_standard_option
('pve-vmid'),
2176 snapname
=> get_standard_option
('pve-snapshot-name'),
2180 description
=> "A textual description or comment.",
2184 returns
=> { type
=> 'null' },
2188 my $rpcenv = PVE
::RPCEnvironment
::get
();
2190 my $authuser = $rpcenv->get_user();
2192 my $vmid = extract_param
($param, 'vmid');
2194 my $snapname = extract_param
($param, 'snapname');
2196 return undef if !defined($param->{description
});
2198 my $updatefn = sub {
2200 my $conf = PVE
::QemuServer
::load_config
($vmid);
2202 PVE
::QemuServer
::check_lock
($conf);
2204 my $snap = $conf->{snapshots
}->{$snapname};
2206 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2208 $snap->{description
} = $param->{description
} if defined($param->{description
});
2210 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2213 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2218 __PACKAGE__-
>register_method({
2219 name
=> 'get_snapshot_config',
2220 path
=> '{vmid}/snapshot/{snapname}/config',
2223 description
=> "Get snapshot configuration",
2225 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2228 additionalProperties
=> 0,
2230 node
=> get_standard_option
('pve-node'),
2231 vmid
=> get_standard_option
('pve-vmid'),
2232 snapname
=> get_standard_option
('pve-snapshot-name'),
2235 returns
=> { type
=> "object" },
2239 my $rpcenv = PVE
::RPCEnvironment
::get
();
2241 my $authuser = $rpcenv->get_user();
2243 my $vmid = extract_param
($param, 'vmid');
2245 my $snapname = extract_param
($param, 'snapname');
2247 my $conf = PVE
::QemuServer
::load_config
($vmid);
2249 my $snap = $conf->{snapshots
}->{$snapname};
2251 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2256 __PACKAGE__-
>register_method({
2258 path
=> '{vmid}/snapshot/{snapname}/rollback',
2262 description
=> "Rollback VM state to specified snapshot.",
2264 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2267 additionalProperties
=> 0,
2269 node
=> get_standard_option
('pve-node'),
2270 vmid
=> get_standard_option
('pve-vmid'),
2271 snapname
=> get_standard_option
('pve-snapshot-name'),
2276 description
=> "the task ID.",
2281 my $rpcenv = PVE
::RPCEnvironment
::get
();
2283 my $authuser = $rpcenv->get_user();
2285 my $node = extract_param
($param, 'node');
2287 my $vmid = extract_param
($param, 'vmid');
2289 my $snapname = extract_param
($param, 'snapname');
2292 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2293 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2296 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2299 __PACKAGE__-
>register_method({
2300 name
=> 'delsnapshot',
2301 path
=> '{vmid}/snapshot/{snapname}',
2305 description
=> "Delete a VM snapshot.",
2307 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2310 additionalProperties
=> 0,
2312 node
=> get_standard_option
('pve-node'),
2313 vmid
=> get_standard_option
('pve-vmid'),
2314 snapname
=> get_standard_option
('pve-snapshot-name'),
2318 description
=> "For removal from config file, even if removing disk snapshots fails.",
2324 description
=> "the task ID.",
2329 my $rpcenv = PVE
::RPCEnvironment
::get
();
2331 my $authuser = $rpcenv->get_user();
2333 my $node = extract_param
($param, 'node');
2335 my $vmid = extract_param
($param, 'vmid');
2337 my $snapname = extract_param
($param, 'snapname');
2340 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2341 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2344 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2347 __PACKAGE__-
>register_method({
2349 path
=> '{vmid}/template',
2353 description
=> "Create a Template.",
2355 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
2357 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
2358 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2362 additionalProperties
=> 0,
2364 node
=> get_standard_option
('pve-node'),
2365 vmid
=> get_standard_option
('pve-vmid'),
2369 description
=> "If you want to convert only 1 disk to base image.",
2370 enum
=> [PVE
::QemuServer
::disknames
()],
2375 returns
=> { type
=> 'null'},
2379 my $rpcenv = PVE
::RPCEnvironment
::get
();
2381 my $authuser = $rpcenv->get_user();
2383 my $node = extract_param
($param, 'node');
2385 my $vmid = extract_param
($param, 'vmid');
2387 my $disk = extract_param
($param, 'disk');
2389 my $updatefn = sub {
2391 my $conf = PVE
::QemuServer
::load_config
($vmid);
2393 PVE
::QemuServer
::check_lock
($conf);
2395 die "you can't convert a template to a template"
2396 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2398 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2400 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2402 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2405 PVE
::QemuServer
::lock_config
($vmid, $updatefn);