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
;
20 use Data
::Dumper
; # fixme: remove
22 use base
qw(PVE::RESTHandler);
24 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.";
26 my $resolve_cdrom_alias = sub {
29 if (my $value = $param->{cdrom
}) {
30 $value .= ",media=cdrom" if $value !~ m/media=/;
31 $param->{ide2
} = $value;
32 delete $param->{cdrom
};
37 my $check_storage_access = sub {
38 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
40 PVE
::QemuServer
::foreach_drive
($settings, sub {
41 my ($ds, $drive) = @_;
43 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
45 my $volid = $drive->{file
};
47 if (!$volid || $volid eq 'none') {
49 } elsif ($isCDROM && ($volid eq 'cdrom')) {
50 $rpcenv->check($authuser, "/", ['Sys.Console']);
51 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
52 my ($storeid, $size) = ($2 || $default_storage, $3);
53 die "no storage ID specified (and no default storage)\n" if !$storeid;
54 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
56 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
61 # Note: $pool is only needed when creating a VM, because pool permissions
62 # are automatically inherited if VM already exists inside a pool.
63 my $create_disks = sub {
64 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
69 PVE
::QemuServer
::foreach_drive
($settings, sub {
72 my $volid = $disk->{file
};
74 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
76 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
77 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
78 my ($storeid, $size) = ($2 || $default_storage, $3);
79 die "no storage ID specified (and no default storage)\n" if !$storeid;
80 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
81 my $fmt = $disk->{format
} || $defformat;
82 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
83 $fmt, undef, $size*1024*1024);
84 $disk->{file
} = $volid;
85 $disk->{size
} = $size*1024*1024*1024;
86 push @$vollist, $volid;
87 delete $disk->{format
}; # no longer needed
88 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
91 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
93 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
95 my $foundvolid = undef;
98 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
99 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
101 PVE
::Storage
::foreach_volid
($dl, sub {
103 if($volumeid eq $volid) {
110 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
112 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
113 $disk->{size
} = $size;
114 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
118 # free allocated images on error
120 syslog
('err', "VM $vmid creating disks failed");
121 foreach my $volid (@$vollist) {
122 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
128 # modify vm config if everything went well
129 foreach my $ds (keys %$res) {
130 $conf->{$ds} = $res->{$ds};
136 my $check_vm_modify_config_perm = sub {
137 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
139 return 1 if $authuser eq 'root@pam';
141 foreach my $opt (@$key_list) {
142 # disk checks need to be done somewhere else
143 next if PVE
::QemuServer
::valid_drivename
($opt);
145 if ($opt eq 'sockets' || $opt eq 'cores' ||
146 $opt eq 'cpu' || $opt eq 'smp' ||
147 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
148 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
149 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
150 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
151 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
152 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
153 } elsif ($opt eq 'args' || $opt eq 'lock') {
154 die "only root can set '$opt' config\n";
155 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
156 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
157 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
158 } elsif ($opt =~ m/^net\d+$/) {
159 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
161 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
168 __PACKAGE__-
>register_method({
172 description
=> "Virtual machine index (per node).",
174 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
178 protected
=> 1, # qemu pid files are only readable by root
180 additionalProperties
=> 0,
182 node
=> get_standard_option
('pve-node'),
191 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
196 my $rpcenv = PVE
::RPCEnvironment
::get
();
197 my $authuser = $rpcenv->get_user();
199 my $vmstatus = PVE
::QemuServer
::vmstatus
();
202 foreach my $vmid (keys %$vmstatus) {
203 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
205 my $data = $vmstatus->{$vmid};
206 $data->{vmid
} = $vmid;
213 __PACKAGE__-
>register_method({
217 description
=> "Create or restore a virtual machine.",
219 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.",
221 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
222 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
228 additionalProperties
=> 0,
229 properties
=> PVE
::QemuServer
::json_config_properties
(
231 node
=> get_standard_option
('pve-node'),
232 vmid
=> get_standard_option
('pve-vmid'),
234 description
=> "The backup file.",
239 storage
=> get_standard_option
('pve-storage-id', {
240 description
=> "Default storage.",
246 description
=> "Allow to overwrite existing VM.",
247 requires
=> 'archive',
252 description
=> "Assign a unique random ethernet address.",
253 requires
=> 'archive',
257 type
=> 'string', format
=> 'pve-poolid',
258 description
=> "Add the VM to the specified pool.",
268 my $rpcenv = PVE
::RPCEnvironment
::get
();
270 my $authuser = $rpcenv->get_user();
272 my $node = extract_param
($param, 'node');
274 my $vmid = extract_param
($param, 'vmid');
276 my $archive = extract_param
($param, 'archive');
278 my $storage = extract_param
($param, 'storage');
280 my $force = extract_param
($param, 'force');
282 my $unique = extract_param
($param, 'unique');
284 my $pool = extract_param
($param, 'pool');
286 my $filename = PVE
::QemuServer
::config_file
($vmid);
288 my $storecfg = PVE
::Storage
::config
();
290 PVE
::Cluster
::check_cfs_quorum
();
292 if (defined($pool)) {
293 $rpcenv->check_pool_exist($pool);
296 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
297 if defined($storage);
300 &$resolve_cdrom_alias($param);
302 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
304 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
306 foreach my $opt (keys %$param) {
307 if (PVE
::QemuServer
::valid_drivename
($opt)) {
308 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
309 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
311 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
312 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
316 PVE
::QemuServer
::add_random_macs
($param);
318 my $keystr = join(' ', keys %$param);
319 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
321 if ($archive eq '-') {
322 die "pipe requires cli environment\n"
323 if $rpcenv->{type
} ne 'cli';
325 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
327 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
328 if PVE
::Storage
::parse_volume_id
($archive, 1);
330 die "can't find archive file '$archive'\n" if !($path && -f
$path);
335 my $addVMtoPoolFn = sub {
336 my $usercfg = cfs_read_file
("user.cfg");
337 if (my $data = $usercfg->{pools
}->{$pool}) {
338 $data->{vms
}->{$vmid} = 1;
339 $usercfg->{vms
}->{$vmid} = $pool;
340 cfs_write_file
("user.cfg", $usercfg);
344 my $restorefn = sub {
347 die "unable to restore vm $vmid: config file already exists\n"
350 die "unable to restore vm $vmid: vm is running\n"
351 if PVE
::QemuServer
::check_running
($vmid);
355 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
358 unique
=> $unique });
360 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
363 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
369 die "unable to create vm $vmid: config file already exists\n"
380 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
382 # try to be smart about bootdisk
383 my @disks = PVE
::QemuServer
::disknames
();
385 foreach my $ds (reverse @disks) {
386 next if !$conf->{$ds};
387 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
388 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
392 if (!$conf->{bootdisk
} && $firstdisk) {
393 $conf->{bootdisk
} = $firstdisk;
396 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
402 foreach my $volid (@$vollist) {
403 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
406 die "create failed - $err";
409 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
412 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
415 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
418 __PACKAGE__-
>register_method({
423 description
=> "Directory index",
428 additionalProperties
=> 0,
430 node
=> get_standard_option
('pve-node'),
431 vmid
=> get_standard_option
('pve-vmid'),
439 subdir
=> { type
=> 'string' },
442 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
448 { subdir
=> 'config' },
449 { subdir
=> 'status' },
450 { subdir
=> 'unlink' },
451 { subdir
=> 'vncproxy' },
452 { subdir
=> 'migrate' },
453 { subdir
=> 'resize' },
455 { subdir
=> 'rrddata' },
456 { subdir
=> 'monitor' },
457 { subdir
=> 'snapshot' },
463 __PACKAGE__-
>register_method({
465 path
=> '{vmid}/rrd',
467 protected
=> 1, # fixme: can we avoid that?
469 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
471 description
=> "Read VM RRD statistics (returns PNG)",
473 additionalProperties
=> 0,
475 node
=> get_standard_option
('pve-node'),
476 vmid
=> get_standard_option
('pve-vmid'),
478 description
=> "Specify the time frame you are interested in.",
480 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
483 description
=> "The list of datasources you want to display.",
484 type
=> 'string', format
=> 'pve-configid-list',
487 description
=> "The RRD consolidation function",
489 enum
=> [ 'AVERAGE', 'MAX' ],
497 filename
=> { type
=> 'string' },
503 return PVE
::Cluster
::create_rrd_graph
(
504 "pve2-vm/$param->{vmid}", $param->{timeframe
},
505 $param->{ds
}, $param->{cf
});
509 __PACKAGE__-
>register_method({
511 path
=> '{vmid}/rrddata',
513 protected
=> 1, # fixme: can we avoid that?
515 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
517 description
=> "Read VM RRD statistics",
519 additionalProperties
=> 0,
521 node
=> get_standard_option
('pve-node'),
522 vmid
=> get_standard_option
('pve-vmid'),
524 description
=> "Specify the time frame you are interested in.",
526 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
529 description
=> "The RRD consolidation function",
531 enum
=> [ 'AVERAGE', 'MAX' ],
546 return PVE
::Cluster
::create_rrd_data
(
547 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
551 __PACKAGE__-
>register_method({
553 path
=> '{vmid}/config',
556 description
=> "Get virtual machine configuration.",
558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
561 additionalProperties
=> 0,
563 node
=> get_standard_option
('pve-node'),
564 vmid
=> get_standard_option
('pve-vmid'),
572 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
579 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
581 delete $conf->{snapshots
};
586 my $vm_is_volid_owner = sub {
587 my ($storecfg, $vmid, $volid) =@_;
589 if ($volid !~ m
|^/|) {
591 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
592 if ($owner && ($owner == $vmid)) {
600 my $test_deallocate_drive = sub {
601 my ($storecfg, $vmid, $key, $drive, $force) = @_;
603 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
604 my $volid = $drive->{file
};
605 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
606 if ($force || $key =~ m/^unused/) {
607 my $sid = PVE
::Storage
::parse_volume_id
($volid);
616 my $delete_drive = sub {
617 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
619 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
620 my $volid = $drive->{file
};
621 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
622 if ($force || $key =~ m/^unused/) {
623 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
626 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
631 delete $conf->{$key};
634 my $vmconfig_delete_option = sub {
635 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
637 return if !defined($conf->{$opt});
639 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
642 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
644 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
645 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
646 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
650 my $unplugwarning = "";
651 if($conf->{ostype
} && $conf->{ostype
} eq 'l26'){
652 $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
653 }elsif($conf->{ostype
} && $conf->{ostype
} eq 'l24'){
654 $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
655 }elsif(!$conf->{ostype
} || ($conf->{ostype
} && $conf->{ostype
} eq 'other')){
656 $unplugwarning = "<br>verify that your guest support acpi hotplug";
659 die "error hot-unplug $opt $unplugwarning" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
661 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt) if $opt eq 'tablet';
664 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
665 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
667 delete $conf->{$opt};
670 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
673 my $safe_num_ne = sub {
676 return 0 if !defined($a) && !defined($b);
677 return 1 if !defined($a);
678 return 1 if !defined($b);
683 my $vmconfig_update_disk = sub {
684 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
686 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
688 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
689 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
691 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
696 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
698 my $media = $drive->{media
} || 'disk';
699 my $oldmedia = $old_drive->{media
} || 'disk';
700 die "unable to change media type\n" if $media ne $oldmedia;
702 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
703 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
705 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
706 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
709 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
710 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
711 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
712 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
713 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
714 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
715 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
716 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
717 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
718 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
723 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
724 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
726 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
727 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
729 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
731 if (PVE
::QemuServer
::check_running
($vmid)) {
732 if ($drive->{file
} eq 'none') {
733 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
735 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
736 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
737 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
741 } else { # hotplug new disks
743 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
747 my $vmconfig_update_net = sub {
748 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
751 #if online update, then unplug first
752 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
755 $conf->{$opt} = $value;
756 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
757 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
759 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
761 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
764 my $vm_config_perm_list = [
774 __PACKAGE__-
>register_method({
776 path
=> '{vmid}/config',
780 description
=> "Set virtual machine options.",
782 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
785 additionalProperties
=> 0,
786 properties
=> PVE
::QemuServer
::json_config_properties
(
788 node
=> get_standard_option
('pve-node'),
789 vmid
=> get_standard_option
('pve-vmid'),
790 skiplock
=> get_standard_option
('skiplock'),
792 type
=> 'string', format
=> 'pve-configid-list',
793 description
=> "A list of settings you want to delete.",
798 description
=> $opt_force_description,
800 requires
=> 'delete',
804 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
810 returns
=> { type
=> 'null'},
814 my $rpcenv = PVE
::RPCEnvironment
::get
();
816 my $authuser = $rpcenv->get_user();
818 my $node = extract_param
($param, 'node');
820 my $vmid = extract_param
($param, 'vmid');
822 my $digest = extract_param
($param, 'digest');
824 my @paramarr = (); # used for log message
825 foreach my $key (keys %$param) {
826 push @paramarr, "-$key", $param->{$key};
829 my $skiplock = extract_param
($param, 'skiplock');
830 raise_param_exc
({ skiplock
=> "Only root may use this option." })
831 if $skiplock && $authuser ne 'root@pam';
833 my $delete_str = extract_param
($param, 'delete');
835 my $force = extract_param
($param, 'force');
837 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
839 my $storecfg = PVE
::Storage
::config
();
841 my $defaults = PVE
::QemuServer
::load_defaults
();
843 &$resolve_cdrom_alias($param);
845 # now try to verify all parameters
848 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
849 $opt = 'ide2' if $opt eq 'cdrom';
850 raise_param_exc
({ delete => "you can't use '-$opt' and " .
851 "-delete $opt' at the same time" })
852 if defined($param->{$opt});
854 if (!PVE
::QemuServer
::option_exists
($opt)) {
855 raise_param_exc
({ delete => "unknown option '$opt'" });
861 foreach my $opt (keys %$param) {
862 if (PVE
::QemuServer
::valid_drivename
($opt)) {
864 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
865 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
866 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
867 } elsif ($opt =~ m/^net(\d+)$/) {
869 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
870 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
874 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
876 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
878 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
882 my $conf = PVE
::QemuServer
::load_config
($vmid);
884 die "checksum missmatch (file change by other user?)\n"
885 if $digest && $digest ne $conf->{digest
};
887 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
889 if ($param->{memory
} || defined($param->{balloon
})) {
890 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
891 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
893 die "balloon value too large (must be smaller than assigned memory)\n"
894 if $balloon > $maxmem;
897 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
899 foreach my $opt (@delete) { # delete
900 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
901 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
904 my $running = PVE
::QemuServer
::check_running
($vmid);
906 foreach my $opt (keys %$param) { # add/change
908 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
910 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
912 if (PVE
::QemuServer
::valid_drivename
($opt)) {
914 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
915 $opt, $param->{$opt}, $force);
917 } elsif ($opt =~ m/^net(\d+)$/) { #nics
919 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
920 $opt, $param->{$opt});
924 if($opt eq 'tablet' && $param->{$opt} == 1){
925 PVE
::QemuServer
::vm_deviceplug
(undef, $conf, $vmid, $opt);
926 }elsif($opt eq 'tablet' && $param->{$opt} == 0){
927 PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
930 $conf->{$opt} = $param->{$opt};
931 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
935 # allow manual ballooning if shares is set to zero
936 if ($running && defined($param->{balloon
}) &&
937 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
938 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
939 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
944 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
950 __PACKAGE__-
>register_method({
951 name
=> 'destroy_vm',
956 description
=> "Destroy the vm (also delete all used/owned volumes).",
958 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
961 additionalProperties
=> 0,
963 node
=> get_standard_option
('pve-node'),
964 vmid
=> get_standard_option
('pve-vmid'),
965 skiplock
=> get_standard_option
('skiplock'),
974 my $rpcenv = PVE
::RPCEnvironment
::get
();
976 my $authuser = $rpcenv->get_user();
978 my $vmid = $param->{vmid
};
980 my $skiplock = $param->{skiplock
};
981 raise_param_exc
({ skiplock
=> "Only root may use this option." })
982 if $skiplock && $authuser ne 'root@pam';
985 my $conf = PVE
::QemuServer
::load_config
($vmid);
987 my $storecfg = PVE
::Storage
::config
();
989 my $delVMfromPoolFn = sub {
990 my $usercfg = cfs_read_file
("user.cfg");
991 if (my $pool = $usercfg->{vms
}->{$vmid}) {
992 if (my $data = $usercfg->{pools
}->{$pool}) {
993 delete $data->{vms
}->{$vmid};
994 delete $usercfg->{vms
}->{$vmid};
995 cfs_write_file
("user.cfg", $usercfg);
1003 syslog
('info', "destroy VM $vmid: $upid\n");
1005 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1007 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
1010 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1013 __PACKAGE__-
>register_method({
1015 path
=> '{vmid}/unlink',
1019 description
=> "Unlink/delete disk images.",
1021 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1024 additionalProperties
=> 0,
1026 node
=> get_standard_option
('pve-node'),
1027 vmid
=> get_standard_option
('pve-vmid'),
1029 type
=> 'string', format
=> 'pve-configid-list',
1030 description
=> "A list of disk IDs you want to delete.",
1034 description
=> $opt_force_description,
1039 returns
=> { type
=> 'null'},
1043 $param->{delete} = extract_param
($param, 'idlist');
1045 __PACKAGE__-
>update_vm($param);
1052 __PACKAGE__-
>register_method({
1054 path
=> '{vmid}/vncproxy',
1058 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1060 description
=> "Creates a TCP VNC proxy connections.",
1062 additionalProperties
=> 0,
1064 node
=> get_standard_option
('pve-node'),
1065 vmid
=> get_standard_option
('pve-vmid'),
1069 additionalProperties
=> 0,
1071 user
=> { type
=> 'string' },
1072 ticket
=> { type
=> 'string' },
1073 cert
=> { type
=> 'string' },
1074 port
=> { type
=> 'integer' },
1075 upid
=> { type
=> 'string' },
1081 my $rpcenv = PVE
::RPCEnvironment
::get
();
1083 my $authuser = $rpcenv->get_user();
1085 my $vmid = $param->{vmid
};
1086 my $node = $param->{node
};
1088 my $authpath = "/vms/$vmid";
1090 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1092 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1095 my $port = PVE
::Tools
::next_vnc_port
();
1099 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1100 $remip = PVE
::Cluster
::remote_node_ip
($node);
1103 # NOTE: kvm VNC traffic is already TLS encrypted
1104 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1111 syslog
('info', "starting vnc proxy $upid\n");
1113 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1115 my $qmstr = join(' ', @$qmcmd);
1117 # also redirect stderr (else we get RFB protocol errors)
1118 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1120 PVE
::Tools
::run_command
($cmd);
1125 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1127 PVE
::Tools
::wait_for_vnc_port
($port);
1138 __PACKAGE__-
>register_method({
1140 path
=> '{vmid}/status',
1143 description
=> "Directory index",
1148 additionalProperties
=> 0,
1150 node
=> get_standard_option
('pve-node'),
1151 vmid
=> get_standard_option
('pve-vmid'),
1159 subdir
=> { type
=> 'string' },
1162 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1168 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1171 { subdir
=> 'current' },
1172 { subdir
=> 'start' },
1173 { subdir
=> 'stop' },
1179 my $vm_is_ha_managed = sub {
1182 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1183 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1189 __PACKAGE__-
>register_method({
1190 name
=> 'vm_status',
1191 path
=> '{vmid}/status/current',
1194 protected
=> 1, # qemu pid files are only readable by root
1195 description
=> "Get virtual machine status.",
1197 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1200 additionalProperties
=> 0,
1202 node
=> get_standard_option
('pve-node'),
1203 vmid
=> get_standard_option
('pve-vmid'),
1206 returns
=> { type
=> 'object' },
1211 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1213 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1214 my $status = $vmstatus->{$param->{vmid
}};
1216 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1221 __PACKAGE__-
>register_method({
1223 path
=> '{vmid}/status/start',
1227 description
=> "Start virtual machine.",
1229 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1232 additionalProperties
=> 0,
1234 node
=> get_standard_option
('pve-node'),
1235 vmid
=> get_standard_option
('pve-vmid'),
1236 skiplock
=> get_standard_option
('skiplock'),
1237 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1238 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1248 my $rpcenv = PVE
::RPCEnvironment
::get
();
1250 my $authuser = $rpcenv->get_user();
1252 my $node = extract_param
($param, 'node');
1254 my $vmid = extract_param
($param, 'vmid');
1256 my $stateuri = extract_param
($param, 'stateuri');
1257 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1258 if $stateuri && $authuser ne 'root@pam';
1260 my $skiplock = extract_param
($param, 'skiplock');
1261 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1262 if $skiplock && $authuser ne 'root@pam';
1264 my $migratedfrom = extract_param
($param, 'migratedfrom');
1265 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1266 if $migratedfrom && $authuser ne 'root@pam';
1268 my $storecfg = PVE
::Storage
::config
();
1270 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1271 $rpcenv->{type
} ne 'ha') {
1276 my $service = "pvevm:$vmid";
1278 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1280 print "Executing HA start for VM $vmid\n";
1282 PVE
::Tools
::run_command
($cmd);
1287 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1294 syslog
('info', "start VM $vmid: $upid\n");
1296 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1301 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1305 __PACKAGE__-
>register_method({
1307 path
=> '{vmid}/status/stop',
1311 description
=> "Stop virtual machine.",
1313 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1316 additionalProperties
=> 0,
1318 node
=> get_standard_option
('pve-node'),
1319 vmid
=> get_standard_option
('pve-vmid'),
1320 skiplock
=> get_standard_option
('skiplock'),
1321 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1323 description
=> "Wait maximal timeout seconds.",
1329 description
=> "Do not decativate storage volumes.",
1342 my $rpcenv = PVE
::RPCEnvironment
::get
();
1344 my $authuser = $rpcenv->get_user();
1346 my $node = extract_param
($param, 'node');
1348 my $vmid = extract_param
($param, 'vmid');
1350 my $skiplock = extract_param
($param, 'skiplock');
1351 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1352 if $skiplock && $authuser ne 'root@pam';
1354 my $keepActive = extract_param
($param, 'keepActive');
1355 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1356 if $keepActive && $authuser ne 'root@pam';
1358 my $migratedfrom = extract_param
($param, 'migratedfrom');
1359 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1360 if $migratedfrom && $authuser ne 'root@pam';
1363 my $storecfg = PVE
::Storage
::config
();
1365 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1370 my $service = "pvevm:$vmid";
1372 my $cmd = ['clusvcadm', '-d', $service];
1374 print "Executing HA stop for VM $vmid\n";
1376 PVE
::Tools
::run_command
($cmd);
1381 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1387 syslog
('info', "stop VM $vmid: $upid\n");
1389 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1390 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1395 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1399 __PACKAGE__-
>register_method({
1401 path
=> '{vmid}/status/reset',
1405 description
=> "Reset virtual machine.",
1407 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1410 additionalProperties
=> 0,
1412 node
=> get_standard_option
('pve-node'),
1413 vmid
=> get_standard_option
('pve-vmid'),
1414 skiplock
=> get_standard_option
('skiplock'),
1423 my $rpcenv = PVE
::RPCEnvironment
::get
();
1425 my $authuser = $rpcenv->get_user();
1427 my $node = extract_param
($param, 'node');
1429 my $vmid = extract_param
($param, 'vmid');
1431 my $skiplock = extract_param
($param, 'skiplock');
1432 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1433 if $skiplock && $authuser ne 'root@pam';
1435 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1440 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1445 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1448 __PACKAGE__-
>register_method({
1449 name
=> 'vm_shutdown',
1450 path
=> '{vmid}/status/shutdown',
1454 description
=> "Shutdown virtual machine.",
1456 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1459 additionalProperties
=> 0,
1461 node
=> get_standard_option
('pve-node'),
1462 vmid
=> get_standard_option
('pve-vmid'),
1463 skiplock
=> get_standard_option
('skiplock'),
1465 description
=> "Wait maximal timeout seconds.",
1471 description
=> "Make sure the VM stops.",
1477 description
=> "Do not decativate storage volumes.",
1490 my $rpcenv = PVE
::RPCEnvironment
::get
();
1492 my $authuser = $rpcenv->get_user();
1494 my $node = extract_param
($param, 'node');
1496 my $vmid = extract_param
($param, 'vmid');
1498 my $skiplock = extract_param
($param, 'skiplock');
1499 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1500 if $skiplock && $authuser ne 'root@pam';
1502 my $keepActive = extract_param
($param, 'keepActive');
1503 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1504 if $keepActive && $authuser ne 'root@pam';
1506 my $storecfg = PVE
::Storage
::config
();
1511 syslog
('info', "shutdown VM $vmid: $upid\n");
1513 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1514 1, $param->{forceStop
}, $keepActive);
1519 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1522 __PACKAGE__-
>register_method({
1523 name
=> 'vm_suspend',
1524 path
=> '{vmid}/status/suspend',
1528 description
=> "Suspend virtual machine.",
1530 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1533 additionalProperties
=> 0,
1535 node
=> get_standard_option
('pve-node'),
1536 vmid
=> get_standard_option
('pve-vmid'),
1537 skiplock
=> get_standard_option
('skiplock'),
1546 my $rpcenv = PVE
::RPCEnvironment
::get
();
1548 my $authuser = $rpcenv->get_user();
1550 my $node = extract_param
($param, 'node');
1552 my $vmid = extract_param
($param, 'vmid');
1554 my $skiplock = extract_param
($param, 'skiplock');
1555 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1556 if $skiplock && $authuser ne 'root@pam';
1558 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1563 syslog
('info', "suspend VM $vmid: $upid\n");
1565 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1570 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1573 __PACKAGE__-
>register_method({
1574 name
=> 'vm_resume',
1575 path
=> '{vmid}/status/resume',
1579 description
=> "Resume virtual machine.",
1581 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1584 additionalProperties
=> 0,
1586 node
=> get_standard_option
('pve-node'),
1587 vmid
=> get_standard_option
('pve-vmid'),
1588 skiplock
=> get_standard_option
('skiplock'),
1597 my $rpcenv = PVE
::RPCEnvironment
::get
();
1599 my $authuser = $rpcenv->get_user();
1601 my $node = extract_param
($param, 'node');
1603 my $vmid = extract_param
($param, 'vmid');
1605 my $skiplock = extract_param
($param, 'skiplock');
1606 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1607 if $skiplock && $authuser ne 'root@pam';
1609 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1614 syslog
('info', "resume VM $vmid: $upid\n");
1616 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1621 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1624 __PACKAGE__-
>register_method({
1625 name
=> 'vm_sendkey',
1626 path
=> '{vmid}/sendkey',
1630 description
=> "Send key event to virtual machine.",
1632 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1635 additionalProperties
=> 0,
1637 node
=> get_standard_option
('pve-node'),
1638 vmid
=> get_standard_option
('pve-vmid'),
1639 skiplock
=> get_standard_option
('skiplock'),
1641 description
=> "The key (qemu monitor encoding).",
1646 returns
=> { type
=> 'null'},
1650 my $rpcenv = PVE
::RPCEnvironment
::get
();
1652 my $authuser = $rpcenv->get_user();
1654 my $node = extract_param
($param, 'node');
1656 my $vmid = extract_param
($param, 'vmid');
1658 my $skiplock = extract_param
($param, 'skiplock');
1659 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1660 if $skiplock && $authuser ne 'root@pam';
1662 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1667 __PACKAGE__-
>register_method({
1668 name
=> 'vm_feature',
1669 path
=> '{vmid}/feature',
1673 description
=> "Check if feature for virtual machine is available.",
1675 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1678 additionalProperties
=> 0,
1680 node
=> get_standard_option
('pve-node'),
1681 vmid
=> get_standard_option
('pve-vmid'),
1683 description
=> "Feature to check.",
1685 enum
=> [ 'snapshot', 'clone' ],
1687 snapname
=> get_standard_option
('pve-snapshot-name', {
1699 my $node = extract_param
($param, 'node');
1701 my $vmid = extract_param
($param, 'vmid');
1703 my $snapname = extract_param
($param, 'snapname');
1705 my $feature = extract_param
($param, 'feature');
1707 my $running = PVE
::QemuServer
::check_running
($vmid);
1709 my $conf = PVE
::QemuServer
::load_config
($vmid);
1712 my $snap = $conf->{snapshots
}->{$snapname};
1713 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1716 my $storecfg = PVE
::Storage
::config
();
1718 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1719 my $res = $hasfeature ?
1 : 0 ;
1723 __PACKAGE__-
>register_method({
1724 name
=> 'migrate_vm',
1725 path
=> '{vmid}/migrate',
1729 description
=> "Migrate virtual machine. Creates a new migration task.",
1731 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1734 additionalProperties
=> 0,
1736 node
=> get_standard_option
('pve-node'),
1737 vmid
=> get_standard_option
('pve-vmid'),
1738 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1741 description
=> "Use online/live migration.",
1746 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1753 description
=> "the task ID.",
1758 my $rpcenv = PVE
::RPCEnvironment
::get
();
1760 my $authuser = $rpcenv->get_user();
1762 my $target = extract_param
($param, 'target');
1764 my $localnode = PVE
::INotify
::nodename
();
1765 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1767 PVE
::Cluster
::check_cfs_quorum
();
1769 PVE
::Cluster
::check_node_exists
($target);
1771 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1773 my $vmid = extract_param
($param, 'vmid');
1775 raise_param_exc
({ force
=> "Only root may use this option." })
1776 if $param->{force
} && $authuser ne 'root@pam';
1779 my $conf = PVE
::QemuServer
::load_config
($vmid);
1781 # try to detect errors early
1783 PVE
::QemuServer
::check_lock
($conf);
1785 if (PVE
::QemuServer
::check_running
($vmid)) {
1786 die "cant migrate running VM without --online\n"
1787 if !$param->{online
};
1790 my $storecfg = PVE
::Storage
::config
();
1791 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1793 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1798 my $service = "pvevm:$vmid";
1800 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1802 print "Executing HA migrate for VM $vmid to node $target\n";
1804 PVE
::Tools
::run_command
($cmd);
1809 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1816 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1819 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1824 __PACKAGE__-
>register_method({
1826 path
=> '{vmid}/monitor',
1830 description
=> "Execute Qemu monitor commands.",
1832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1835 additionalProperties
=> 0,
1837 node
=> get_standard_option
('pve-node'),
1838 vmid
=> get_standard_option
('pve-vmid'),
1841 description
=> "The monitor command.",
1845 returns
=> { type
=> 'string'},
1849 my $vmid = $param->{vmid
};
1851 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1855 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1857 $res = "ERROR: $@" if $@;
1862 __PACKAGE__-
>register_method({
1863 name
=> 'resize_vm',
1864 path
=> '{vmid}/resize',
1868 description
=> "Extend volume size.",
1870 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1873 additionalProperties
=> 0,
1875 node
=> get_standard_option
('pve-node'),
1876 vmid
=> get_standard_option
('pve-vmid'),
1877 skiplock
=> get_standard_option
('skiplock'),
1880 description
=> "The disk you want to resize.",
1881 enum
=> [PVE
::QemuServer
::disknames
()],
1885 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1886 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.",
1890 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1896 returns
=> { type
=> 'null'},
1900 my $rpcenv = PVE
::RPCEnvironment
::get
();
1902 my $authuser = $rpcenv->get_user();
1904 my $node = extract_param
($param, 'node');
1906 my $vmid = extract_param
($param, 'vmid');
1908 my $digest = extract_param
($param, 'digest');
1910 my $disk = extract_param
($param, 'disk');
1912 my $sizestr = extract_param
($param, 'size');
1914 my $skiplock = extract_param
($param, 'skiplock');
1915 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1916 if $skiplock && $authuser ne 'root@pam';
1918 my $storecfg = PVE
::Storage
::config
();
1920 my $updatefn = sub {
1922 my $conf = PVE
::QemuServer
::load_config
($vmid);
1924 die "checksum missmatch (file change by other user?)\n"
1925 if $digest && $digest ne $conf->{digest
};
1926 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1928 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1930 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1932 my $volid = $drive->{file
};
1934 die "disk '$disk' has no associated volume\n" if !$volid;
1936 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1938 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1940 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1942 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1944 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1945 my ($ext, $newsize, $unit) = ($1, $2, $4);
1948 $newsize = $newsize * 1024;
1949 } elsif ($unit eq 'M') {
1950 $newsize = $newsize * 1024 * 1024;
1951 } elsif ($unit eq 'G') {
1952 $newsize = $newsize * 1024 * 1024 * 1024;
1953 } elsif ($unit eq 'T') {
1954 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1957 $newsize += $size if $ext;
1958 $newsize = int($newsize);
1960 die "unable to skrink disk size\n" if $newsize < $size;
1962 return if $size == $newsize;
1964 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1966 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1968 $drive->{size
} = $newsize;
1969 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1971 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1974 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1978 __PACKAGE__-
>register_method({
1979 name
=> 'snapshot_list',
1980 path
=> '{vmid}/snapshot',
1982 description
=> "List all snapshots.",
1984 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1987 protected
=> 1, # qemu pid files are only readable by root
1989 additionalProperties
=> 0,
1991 vmid
=> get_standard_option
('pve-vmid'),
1992 node
=> get_standard_option
('pve-node'),
2001 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2006 my $vmid = $param->{vmid
};
2008 my $conf = PVE
::QemuServer
::load_config
($vmid);
2009 my $snaphash = $conf->{snapshots
} || {};
2013 foreach my $name (keys %$snaphash) {
2014 my $d = $snaphash->{$name};
2017 snaptime
=> $d->{snaptime
} || 0,
2018 vmstate
=> $d->{vmstate
} ?
1 : 0,
2019 description
=> $d->{description
} || '',
2021 $item->{parent
} = $d->{parent
} if $d->{parent
};
2022 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2026 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2027 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2028 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2030 push @$res, $current;
2035 __PACKAGE__-
>register_method({
2037 path
=> '{vmid}/snapshot',
2041 description
=> "Snapshot a VM.",
2043 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2046 additionalProperties
=> 0,
2048 node
=> get_standard_option
('pve-node'),
2049 vmid
=> get_standard_option
('pve-vmid'),
2050 snapname
=> get_standard_option
('pve-snapshot-name'),
2054 description
=> "Save the vmstate",
2059 description
=> "Freeze the filesystem",
2064 description
=> "A textual description or comment.",
2070 description
=> "the task ID.",
2075 my $rpcenv = PVE
::RPCEnvironment
::get
();
2077 my $authuser = $rpcenv->get_user();
2079 my $node = extract_param
($param, 'node');
2081 my $vmid = extract_param
($param, 'vmid');
2083 my $snapname = extract_param
($param, 'snapname');
2085 die "unable to use snapshot name 'current' (reserved name)\n"
2086 if $snapname eq 'current';
2089 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2090 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2091 $param->{freezefs
}, $param->{description
});
2094 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2097 __PACKAGE__-
>register_method({
2098 name
=> 'snapshot_cmd_idx',
2099 path
=> '{vmid}/snapshot/{snapname}',
2106 additionalProperties
=> 0,
2108 vmid
=> get_standard_option
('pve-vmid'),
2109 node
=> get_standard_option
('pve-node'),
2110 snapname
=> get_standard_option
('pve-snapshot-name'),
2119 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2126 push @$res, { cmd
=> 'rollback' };
2127 push @$res, { cmd
=> 'config' };
2132 __PACKAGE__-
>register_method({
2133 name
=> 'update_snapshot_config',
2134 path
=> '{vmid}/snapshot/{snapname}/config',
2138 description
=> "Update snapshot metadata.",
2140 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2143 additionalProperties
=> 0,
2145 node
=> get_standard_option
('pve-node'),
2146 vmid
=> get_standard_option
('pve-vmid'),
2147 snapname
=> get_standard_option
('pve-snapshot-name'),
2151 description
=> "A textual description or comment.",
2155 returns
=> { type
=> 'null' },
2159 my $rpcenv = PVE
::RPCEnvironment
::get
();
2161 my $authuser = $rpcenv->get_user();
2163 my $vmid = extract_param
($param, 'vmid');
2165 my $snapname = extract_param
($param, 'snapname');
2167 return undef if !defined($param->{description
});
2169 my $updatefn = sub {
2171 my $conf = PVE
::QemuServer
::load_config
($vmid);
2173 PVE
::QemuServer
::check_lock
($conf);
2175 my $snap = $conf->{snapshots
}->{$snapname};
2177 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2179 $snap->{description
} = $param->{description
} if defined($param->{description
});
2181 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2184 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2189 __PACKAGE__-
>register_method({
2190 name
=> 'get_snapshot_config',
2191 path
=> '{vmid}/snapshot/{snapname}/config',
2194 description
=> "Get snapshot configuration",
2196 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2199 additionalProperties
=> 0,
2201 node
=> get_standard_option
('pve-node'),
2202 vmid
=> get_standard_option
('pve-vmid'),
2203 snapname
=> get_standard_option
('pve-snapshot-name'),
2206 returns
=> { type
=> "object" },
2210 my $rpcenv = PVE
::RPCEnvironment
::get
();
2212 my $authuser = $rpcenv->get_user();
2214 my $vmid = extract_param
($param, 'vmid');
2216 my $snapname = extract_param
($param, 'snapname');
2218 my $conf = PVE
::QemuServer
::load_config
($vmid);
2220 my $snap = $conf->{snapshots
}->{$snapname};
2222 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2227 __PACKAGE__-
>register_method({
2229 path
=> '{vmid}/snapshot/{snapname}/rollback',
2233 description
=> "Rollback VM state to specified snapshot.",
2235 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2238 additionalProperties
=> 0,
2240 node
=> get_standard_option
('pve-node'),
2241 vmid
=> get_standard_option
('pve-vmid'),
2242 snapname
=> get_standard_option
('pve-snapshot-name'),
2247 description
=> "the task ID.",
2252 my $rpcenv = PVE
::RPCEnvironment
::get
();
2254 my $authuser = $rpcenv->get_user();
2256 my $node = extract_param
($param, 'node');
2258 my $vmid = extract_param
($param, 'vmid');
2260 my $snapname = extract_param
($param, 'snapname');
2263 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2264 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2267 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2270 __PACKAGE__-
>register_method({
2271 name
=> 'delsnapshot',
2272 path
=> '{vmid}/snapshot/{snapname}',
2276 description
=> "Delete a VM snapshot.",
2278 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2281 additionalProperties
=> 0,
2283 node
=> get_standard_option
('pve-node'),
2284 vmid
=> get_standard_option
('pve-vmid'),
2285 snapname
=> get_standard_option
('pve-snapshot-name'),
2289 description
=> "For removal from config file, even if removing disk snapshots fails.",
2295 description
=> "the task ID.",
2300 my $rpcenv = PVE
::RPCEnvironment
::get
();
2302 my $authuser = $rpcenv->get_user();
2304 my $node = extract_param
($param, 'node');
2306 my $vmid = extract_param
($param, 'vmid');
2308 my $snapname = extract_param
($param, 'snapname');
2311 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2312 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2315 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2318 __PACKAGE__-
>register_method({
2320 path
=> '{vmid}/template',
2324 description
=> "Create a Template.",
2326 additionalProperties
=> 0,
2328 node
=> get_standard_option
('pve-node'),
2329 vmid
=> get_standard_option
('pve-vmid'),
2333 description
=> "If you want to convert only 1 disk to base image.",
2334 enum
=> [PVE
::QemuServer
::disknames
()],
2339 returns
=> { type
=> 'null'},
2343 my $rpcenv = PVE
::RPCEnvironment
::get
();
2345 my $authuser = $rpcenv->get_user();
2347 my $node = extract_param
($param, 'node');
2349 my $vmid = extract_param
($param, 'vmid');
2351 my $disk = extract_param
($param, 'disk');
2353 my $updatefn = sub {
2355 my $conf = PVE
::QemuServer
::load_config
($vmid);
2357 PVE
::QemuServer
::check_lock
($conf);
2359 die "you can't convert a template to a template"
2360 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2362 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2364 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2366 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2369 PVE
::QemuServer
::lock_config
($vmid, $updatefn);