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);
353 # destroy existing data - keep empty config
354 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, 1);
358 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
361 unique
=> $unique });
363 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
366 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
372 die "unable to create vm $vmid: config file already exists\n"
383 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
385 # try to be smart about bootdisk
386 my @disks = PVE
::QemuServer
::disknames
();
388 foreach my $ds (reverse @disks) {
389 next if !$conf->{$ds};
390 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
391 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
395 if (!$conf->{bootdisk
} && $firstdisk) {
396 $conf->{bootdisk
} = $firstdisk;
399 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
405 foreach my $volid (@$vollist) {
406 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
409 die "create failed - $err";
412 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
415 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
418 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
421 __PACKAGE__-
>register_method({
426 description
=> "Directory index",
431 additionalProperties
=> 0,
433 node
=> get_standard_option
('pve-node'),
434 vmid
=> get_standard_option
('pve-vmid'),
442 subdir
=> { type
=> 'string' },
445 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
451 { subdir
=> 'config' },
452 { subdir
=> 'status' },
453 { subdir
=> 'unlink' },
454 { subdir
=> 'vncproxy' },
455 { subdir
=> 'migrate' },
456 { subdir
=> 'resize' },
458 { subdir
=> 'rrddata' },
459 { subdir
=> 'monitor' },
460 { subdir
=> 'snapshot' },
466 __PACKAGE__-
>register_method({
468 path
=> '{vmid}/rrd',
470 protected
=> 1, # fixme: can we avoid that?
472 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
474 description
=> "Read VM RRD statistics (returns PNG)",
476 additionalProperties
=> 0,
478 node
=> get_standard_option
('pve-node'),
479 vmid
=> get_standard_option
('pve-vmid'),
481 description
=> "Specify the time frame you are interested in.",
483 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
486 description
=> "The list of datasources you want to display.",
487 type
=> 'string', format
=> 'pve-configid-list',
490 description
=> "The RRD consolidation function",
492 enum
=> [ 'AVERAGE', 'MAX' ],
500 filename
=> { type
=> 'string' },
506 return PVE
::Cluster
::create_rrd_graph
(
507 "pve2-vm/$param->{vmid}", $param->{timeframe
},
508 $param->{ds
}, $param->{cf
});
512 __PACKAGE__-
>register_method({
514 path
=> '{vmid}/rrddata',
516 protected
=> 1, # fixme: can we avoid that?
518 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
520 description
=> "Read VM RRD statistics",
522 additionalProperties
=> 0,
524 node
=> get_standard_option
('pve-node'),
525 vmid
=> get_standard_option
('pve-vmid'),
527 description
=> "Specify the time frame you are interested in.",
529 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
532 description
=> "The RRD consolidation function",
534 enum
=> [ 'AVERAGE', 'MAX' ],
549 return PVE
::Cluster
::create_rrd_data
(
550 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
554 __PACKAGE__-
>register_method({
556 path
=> '{vmid}/config',
559 description
=> "Get virtual machine configuration.",
561 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
564 additionalProperties
=> 0,
566 node
=> get_standard_option
('pve-node'),
567 vmid
=> get_standard_option
('pve-vmid'),
575 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
582 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
584 delete $conf->{snapshots
};
589 my $vm_is_volid_owner = sub {
590 my ($storecfg, $vmid, $volid) =@_;
592 if ($volid !~ m
|^/|) {
594 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
595 if ($owner && ($owner == $vmid)) {
603 my $test_deallocate_drive = sub {
604 my ($storecfg, $vmid, $key, $drive, $force) = @_;
606 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
607 my $volid = $drive->{file
};
608 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
609 if ($force || $key =~ m/^unused/) {
610 my $sid = PVE
::Storage
::parse_volume_id
($volid);
619 my $delete_drive = sub {
620 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
622 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
623 my $volid = $drive->{file
};
624 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
625 if ($force || $key =~ m/^unused/) {
626 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
629 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
634 delete $conf->{$key};
637 my $vmconfig_delete_option = sub {
638 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
640 return if !defined($conf->{$opt});
642 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
645 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
647 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
648 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
649 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
653 die "error hot-unplug $opt" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
656 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
657 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
659 delete $conf->{$opt};
662 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
665 my $safe_num_ne = sub {
668 return 0 if !defined($a) && !defined($b);
669 return 1 if !defined($a);
670 return 1 if !defined($b);
675 my $vmconfig_update_disk = sub {
676 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
678 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
680 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
681 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
683 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
688 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
690 my $media = $drive->{media
} || 'disk';
691 my $oldmedia = $old_drive->{media
} || 'disk';
692 die "unable to change media type\n" if $media ne $oldmedia;
694 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
695 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
697 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
698 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
701 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
702 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
703 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
704 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
705 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
706 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
707 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
708 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
709 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
710 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
715 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
716 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
718 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
719 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
721 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
723 if (PVE
::QemuServer
::check_running
($vmid)) {
724 if ($drive->{file
} eq 'none') {
725 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
727 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
728 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
729 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
733 } else { # hotplug new disks
735 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
739 my $vmconfig_update_net = sub {
740 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
743 #if online update, then unplug first
744 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
747 $conf->{$opt} = $value;
748 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
749 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
751 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
753 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
756 my $vm_config_perm_list = [
766 __PACKAGE__-
>register_method({
768 path
=> '{vmid}/config',
772 description
=> "Set virtual machine options.",
774 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
777 additionalProperties
=> 0,
778 properties
=> PVE
::QemuServer
::json_config_properties
(
780 node
=> get_standard_option
('pve-node'),
781 vmid
=> get_standard_option
('pve-vmid'),
782 skiplock
=> get_standard_option
('skiplock'),
784 type
=> 'string', format
=> 'pve-configid-list',
785 description
=> "A list of settings you want to delete.",
790 description
=> $opt_force_description,
792 requires
=> 'delete',
796 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
802 returns
=> { type
=> 'null'},
806 my $rpcenv = PVE
::RPCEnvironment
::get
();
808 my $authuser = $rpcenv->get_user();
810 my $node = extract_param
($param, 'node');
812 my $vmid = extract_param
($param, 'vmid');
814 my $digest = extract_param
($param, 'digest');
816 my @paramarr = (); # used for log message
817 foreach my $key (keys %$param) {
818 push @paramarr, "-$key", $param->{$key};
821 my $skiplock = extract_param
($param, 'skiplock');
822 raise_param_exc
({ skiplock
=> "Only root may use this option." })
823 if $skiplock && $authuser ne 'root@pam';
825 my $delete_str = extract_param
($param, 'delete');
827 my $force = extract_param
($param, 'force');
829 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
831 my $storecfg = PVE
::Storage
::config
();
833 my $defaults = PVE
::QemuServer
::load_defaults
();
835 &$resolve_cdrom_alias($param);
837 # now try to verify all parameters
840 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
841 $opt = 'ide2' if $opt eq 'cdrom';
842 raise_param_exc
({ delete => "you can't use '-$opt' and " .
843 "-delete $opt' at the same time" })
844 if defined($param->{$opt});
846 if (!PVE
::QemuServer
::option_exists
($opt)) {
847 raise_param_exc
({ delete => "unknown option '$opt'" });
853 foreach my $opt (keys %$param) {
854 if (PVE
::QemuServer
::valid_drivename
($opt)) {
856 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
857 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
858 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
859 } elsif ($opt =~ m/^net(\d+)$/) {
861 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
862 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
866 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
868 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
870 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
874 my $conf = PVE
::QemuServer
::load_config
($vmid);
876 die "checksum missmatch (file change by other user?)\n"
877 if $digest && $digest ne $conf->{digest
};
879 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
881 if ($param->{memory
} || defined($param->{balloon
})) {
882 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
883 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
885 die "balloon value too large (must be smaller than assigned memory)\n"
886 if $balloon > $maxmem;
889 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
891 foreach my $opt (@delete) { # delete
892 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
893 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
896 my $running = PVE
::QemuServer
::check_running
($vmid);
898 foreach my $opt (keys %$param) { # add/change
900 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
902 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
904 if (PVE
::QemuServer
::valid_drivename
($opt)) {
906 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
907 $opt, $param->{$opt}, $force);
909 } elsif ($opt =~ m/^net(\d+)$/) { #nics
911 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
912 $opt, $param->{$opt});
916 $conf->{$opt} = $param->{$opt};
917 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
921 # allow manual ballooning if shares is set to zero
922 if ($running && defined($param->{balloon
}) &&
923 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
924 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
925 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
930 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
936 __PACKAGE__-
>register_method({
937 name
=> 'destroy_vm',
942 description
=> "Destroy the vm (also delete all used/owned volumes).",
944 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
947 additionalProperties
=> 0,
949 node
=> get_standard_option
('pve-node'),
950 vmid
=> get_standard_option
('pve-vmid'),
951 skiplock
=> get_standard_option
('skiplock'),
960 my $rpcenv = PVE
::RPCEnvironment
::get
();
962 my $authuser = $rpcenv->get_user();
964 my $vmid = $param->{vmid
};
966 my $skiplock = $param->{skiplock
};
967 raise_param_exc
({ skiplock
=> "Only root may use this option." })
968 if $skiplock && $authuser ne 'root@pam';
971 my $conf = PVE
::QemuServer
::load_config
($vmid);
973 my $storecfg = PVE
::Storage
::config
();
975 my $delVMfromPoolFn = sub {
976 my $usercfg = cfs_read_file
("user.cfg");
977 if (my $pool = $usercfg->{vms
}->{$vmid}) {
978 if (my $data = $usercfg->{pools
}->{$pool}) {
979 delete $data->{vms
}->{$vmid};
980 delete $usercfg->{vms
}->{$vmid};
981 cfs_write_file
("user.cfg", $usercfg);
989 syslog
('info', "destroy VM $vmid: $upid\n");
991 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
993 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
996 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
999 __PACKAGE__-
>register_method({
1001 path
=> '{vmid}/unlink',
1005 description
=> "Unlink/delete disk images.",
1007 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1010 additionalProperties
=> 0,
1012 node
=> get_standard_option
('pve-node'),
1013 vmid
=> get_standard_option
('pve-vmid'),
1015 type
=> 'string', format
=> 'pve-configid-list',
1016 description
=> "A list of disk IDs you want to delete.",
1020 description
=> $opt_force_description,
1025 returns
=> { type
=> 'null'},
1029 $param->{delete} = extract_param
($param, 'idlist');
1031 __PACKAGE__-
>update_vm($param);
1038 __PACKAGE__-
>register_method({
1040 path
=> '{vmid}/vncproxy',
1044 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1046 description
=> "Creates a TCP VNC proxy connections.",
1048 additionalProperties
=> 0,
1050 node
=> get_standard_option
('pve-node'),
1051 vmid
=> get_standard_option
('pve-vmid'),
1055 additionalProperties
=> 0,
1057 user
=> { type
=> 'string' },
1058 ticket
=> { type
=> 'string' },
1059 cert
=> { type
=> 'string' },
1060 port
=> { type
=> 'integer' },
1061 upid
=> { type
=> 'string' },
1067 my $rpcenv = PVE
::RPCEnvironment
::get
();
1069 my $authuser = $rpcenv->get_user();
1071 my $vmid = $param->{vmid
};
1072 my $node = $param->{node
};
1074 my $authpath = "/vms/$vmid";
1076 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1078 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1081 my $port = PVE
::Tools
::next_vnc_port
();
1085 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1086 $remip = PVE
::Cluster
::remote_node_ip
($node);
1089 # NOTE: kvm VNC traffic is already TLS encrypted
1090 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1097 syslog
('info', "starting vnc proxy $upid\n");
1099 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1101 my $qmstr = join(' ', @$qmcmd);
1103 # also redirect stderr (else we get RFB protocol errors)
1104 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1106 PVE
::Tools
::run_command
($cmd);
1111 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1113 PVE
::Tools
::wait_for_vnc_port
($port);
1124 __PACKAGE__-
>register_method({
1126 path
=> '{vmid}/status',
1129 description
=> "Directory index",
1134 additionalProperties
=> 0,
1136 node
=> get_standard_option
('pve-node'),
1137 vmid
=> get_standard_option
('pve-vmid'),
1145 subdir
=> { type
=> 'string' },
1148 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1154 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1157 { subdir
=> 'current' },
1158 { subdir
=> 'start' },
1159 { subdir
=> 'stop' },
1165 my $vm_is_ha_managed = sub {
1168 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1169 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1175 __PACKAGE__-
>register_method({
1176 name
=> 'vm_status',
1177 path
=> '{vmid}/status/current',
1180 protected
=> 1, # qemu pid files are only readable by root
1181 description
=> "Get virtual machine status.",
1183 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1186 additionalProperties
=> 0,
1188 node
=> get_standard_option
('pve-node'),
1189 vmid
=> get_standard_option
('pve-vmid'),
1192 returns
=> { type
=> 'object' },
1197 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1199 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1200 my $status = $vmstatus->{$param->{vmid
}};
1202 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1207 __PACKAGE__-
>register_method({
1209 path
=> '{vmid}/status/start',
1213 description
=> "Start virtual machine.",
1215 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1218 additionalProperties
=> 0,
1220 node
=> get_standard_option
('pve-node'),
1221 vmid
=> get_standard_option
('pve-vmid'),
1222 skiplock
=> get_standard_option
('skiplock'),
1223 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1224 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1234 my $rpcenv = PVE
::RPCEnvironment
::get
();
1236 my $authuser = $rpcenv->get_user();
1238 my $node = extract_param
($param, 'node');
1240 my $vmid = extract_param
($param, 'vmid');
1242 my $stateuri = extract_param
($param, 'stateuri');
1243 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1244 if $stateuri && $authuser ne 'root@pam';
1246 my $skiplock = extract_param
($param, 'skiplock');
1247 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1248 if $skiplock && $authuser ne 'root@pam';
1250 my $migratedfrom = extract_param
($param, 'migratedfrom');
1251 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1252 if $migratedfrom && $authuser ne 'root@pam';
1254 my $storecfg = PVE
::Storage
::config
();
1256 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1257 $rpcenv->{type
} ne 'ha') {
1262 my $service = "pvevm:$vmid";
1264 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1266 print "Executing HA start for VM $vmid\n";
1268 PVE
::Tools
::run_command
($cmd);
1273 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1280 syslog
('info', "start VM $vmid: $upid\n");
1282 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1287 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1291 __PACKAGE__-
>register_method({
1293 path
=> '{vmid}/status/stop',
1297 description
=> "Stop virtual machine.",
1299 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1302 additionalProperties
=> 0,
1304 node
=> get_standard_option
('pve-node'),
1305 vmid
=> get_standard_option
('pve-vmid'),
1306 skiplock
=> get_standard_option
('skiplock'),
1307 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1309 description
=> "Wait maximal timeout seconds.",
1315 description
=> "Do not decativate storage volumes.",
1328 my $rpcenv = PVE
::RPCEnvironment
::get
();
1330 my $authuser = $rpcenv->get_user();
1332 my $node = extract_param
($param, 'node');
1334 my $vmid = extract_param
($param, 'vmid');
1336 my $skiplock = extract_param
($param, 'skiplock');
1337 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1338 if $skiplock && $authuser ne 'root@pam';
1340 my $keepActive = extract_param
($param, 'keepActive');
1341 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1342 if $keepActive && $authuser ne 'root@pam';
1344 my $migratedfrom = extract_param
($param, 'migratedfrom');
1345 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1346 if $migratedfrom && $authuser ne 'root@pam';
1349 my $storecfg = PVE
::Storage
::config
();
1351 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1356 my $service = "pvevm:$vmid";
1358 my $cmd = ['clusvcadm', '-d', $service];
1360 print "Executing HA stop for VM $vmid\n";
1362 PVE
::Tools
::run_command
($cmd);
1367 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1373 syslog
('info', "stop VM $vmid: $upid\n");
1375 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1376 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1381 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1385 __PACKAGE__-
>register_method({
1387 path
=> '{vmid}/status/reset',
1391 description
=> "Reset virtual machine.",
1393 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1396 additionalProperties
=> 0,
1398 node
=> get_standard_option
('pve-node'),
1399 vmid
=> get_standard_option
('pve-vmid'),
1400 skiplock
=> get_standard_option
('skiplock'),
1409 my $rpcenv = PVE
::RPCEnvironment
::get
();
1411 my $authuser = $rpcenv->get_user();
1413 my $node = extract_param
($param, 'node');
1415 my $vmid = extract_param
($param, 'vmid');
1417 my $skiplock = extract_param
($param, 'skiplock');
1418 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1419 if $skiplock && $authuser ne 'root@pam';
1421 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1426 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1431 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1434 __PACKAGE__-
>register_method({
1435 name
=> 'vm_shutdown',
1436 path
=> '{vmid}/status/shutdown',
1440 description
=> "Shutdown virtual machine.",
1442 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1445 additionalProperties
=> 0,
1447 node
=> get_standard_option
('pve-node'),
1448 vmid
=> get_standard_option
('pve-vmid'),
1449 skiplock
=> get_standard_option
('skiplock'),
1451 description
=> "Wait maximal timeout seconds.",
1457 description
=> "Make sure the VM stops.",
1463 description
=> "Do not decativate storage volumes.",
1476 my $rpcenv = PVE
::RPCEnvironment
::get
();
1478 my $authuser = $rpcenv->get_user();
1480 my $node = extract_param
($param, 'node');
1482 my $vmid = extract_param
($param, 'vmid');
1484 my $skiplock = extract_param
($param, 'skiplock');
1485 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1486 if $skiplock && $authuser ne 'root@pam';
1488 my $keepActive = extract_param
($param, 'keepActive');
1489 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1490 if $keepActive && $authuser ne 'root@pam';
1492 my $storecfg = PVE
::Storage
::config
();
1497 syslog
('info', "shutdown VM $vmid: $upid\n");
1499 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1500 1, $param->{forceStop
}, $keepActive);
1505 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1508 __PACKAGE__-
>register_method({
1509 name
=> 'vm_suspend',
1510 path
=> '{vmid}/status/suspend',
1514 description
=> "Suspend virtual machine.",
1516 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1519 additionalProperties
=> 0,
1521 node
=> get_standard_option
('pve-node'),
1522 vmid
=> get_standard_option
('pve-vmid'),
1523 skiplock
=> get_standard_option
('skiplock'),
1532 my $rpcenv = PVE
::RPCEnvironment
::get
();
1534 my $authuser = $rpcenv->get_user();
1536 my $node = extract_param
($param, 'node');
1538 my $vmid = extract_param
($param, 'vmid');
1540 my $skiplock = extract_param
($param, 'skiplock');
1541 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1542 if $skiplock && $authuser ne 'root@pam';
1544 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1549 syslog
('info', "suspend VM $vmid: $upid\n");
1551 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1556 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1559 __PACKAGE__-
>register_method({
1560 name
=> 'vm_resume',
1561 path
=> '{vmid}/status/resume',
1565 description
=> "Resume virtual machine.",
1567 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1570 additionalProperties
=> 0,
1572 node
=> get_standard_option
('pve-node'),
1573 vmid
=> get_standard_option
('pve-vmid'),
1574 skiplock
=> get_standard_option
('skiplock'),
1583 my $rpcenv = PVE
::RPCEnvironment
::get
();
1585 my $authuser = $rpcenv->get_user();
1587 my $node = extract_param
($param, 'node');
1589 my $vmid = extract_param
($param, 'vmid');
1591 my $skiplock = extract_param
($param, 'skiplock');
1592 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1593 if $skiplock && $authuser ne 'root@pam';
1595 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1600 syslog
('info', "resume VM $vmid: $upid\n");
1602 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1607 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1610 __PACKAGE__-
>register_method({
1611 name
=> 'vm_sendkey',
1612 path
=> '{vmid}/sendkey',
1616 description
=> "Send key event to virtual machine.",
1618 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1621 additionalProperties
=> 0,
1623 node
=> get_standard_option
('pve-node'),
1624 vmid
=> get_standard_option
('pve-vmid'),
1625 skiplock
=> get_standard_option
('skiplock'),
1627 description
=> "The key (qemu monitor encoding).",
1632 returns
=> { type
=> 'null'},
1636 my $rpcenv = PVE
::RPCEnvironment
::get
();
1638 my $authuser = $rpcenv->get_user();
1640 my $node = extract_param
($param, 'node');
1642 my $vmid = extract_param
($param, 'vmid');
1644 my $skiplock = extract_param
($param, 'skiplock');
1645 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1646 if $skiplock && $authuser ne 'root@pam';
1648 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1653 __PACKAGE__-
>register_method({
1654 name
=> 'vm_feature',
1655 path
=> '{vmid}/feature',
1659 description
=> "Check if feature for virtual machine is available.",
1661 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1664 additionalProperties
=> 0,
1666 node
=> get_standard_option
('pve-node'),
1667 vmid
=> get_standard_option
('pve-vmid'),
1669 description
=> "Feature to check.",
1671 enum
=> [ 'snapshot', 'clone' ],
1673 snapname
=> get_standard_option
('pve-snapshot-name', {
1685 my $node = extract_param
($param, 'node');
1687 my $vmid = extract_param
($param, 'vmid');
1689 my $snapname = extract_param
($param, 'snapname');
1691 my $feature = extract_param
($param, 'feature');
1693 my $running = PVE
::QemuServer
::check_running
($vmid);
1695 my $conf = PVE
::QemuServer
::load_config
($vmid);
1698 my $snap = $conf->{snapshots
}->{$snapname};
1699 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1702 my $storecfg = PVE
::Storage
::config
();
1704 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1705 my $res = $hasfeature ?
1 : 0 ;
1709 __PACKAGE__-
>register_method({
1710 name
=> 'migrate_vm',
1711 path
=> '{vmid}/migrate',
1715 description
=> "Migrate virtual machine. Creates a new migration task.",
1717 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1720 additionalProperties
=> 0,
1722 node
=> get_standard_option
('pve-node'),
1723 vmid
=> get_standard_option
('pve-vmid'),
1724 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1727 description
=> "Use online/live migration.",
1732 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1739 description
=> "the task ID.",
1744 my $rpcenv = PVE
::RPCEnvironment
::get
();
1746 my $authuser = $rpcenv->get_user();
1748 my $target = extract_param
($param, 'target');
1750 my $localnode = PVE
::INotify
::nodename
();
1751 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1753 PVE
::Cluster
::check_cfs_quorum
();
1755 PVE
::Cluster
::check_node_exists
($target);
1757 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1759 my $vmid = extract_param
($param, 'vmid');
1761 raise_param_exc
({ force
=> "Only root may use this option." })
1762 if $param->{force
} && $authuser ne 'root@pam';
1765 my $conf = PVE
::QemuServer
::load_config
($vmid);
1767 # try to detect errors early
1769 PVE
::QemuServer
::check_lock
($conf);
1771 if (PVE
::QemuServer
::check_running
($vmid)) {
1772 die "cant migrate running VM without --online\n"
1773 if !$param->{online
};
1776 my $storecfg = PVE
::Storage
::config
();
1777 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1779 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1784 my $service = "pvevm:$vmid";
1786 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1788 print "Executing HA migrate for VM $vmid to node $target\n";
1790 PVE
::Tools
::run_command
($cmd);
1795 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1802 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1805 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1810 __PACKAGE__-
>register_method({
1812 path
=> '{vmid}/monitor',
1816 description
=> "Execute Qemu monitor commands.",
1818 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1821 additionalProperties
=> 0,
1823 node
=> get_standard_option
('pve-node'),
1824 vmid
=> get_standard_option
('pve-vmid'),
1827 description
=> "The monitor command.",
1831 returns
=> { type
=> 'string'},
1835 my $vmid = $param->{vmid
};
1837 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1841 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1843 $res = "ERROR: $@" if $@;
1848 __PACKAGE__-
>register_method({
1849 name
=> 'resize_vm',
1850 path
=> '{vmid}/resize',
1854 description
=> "Extend volume size.",
1856 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1859 additionalProperties
=> 0,
1861 node
=> get_standard_option
('pve-node'),
1862 vmid
=> get_standard_option
('pve-vmid'),
1863 skiplock
=> get_standard_option
('skiplock'),
1866 description
=> "The disk you want to resize.",
1867 enum
=> [PVE
::QemuServer
::disknames
()],
1871 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1872 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.",
1876 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1882 returns
=> { type
=> 'null'},
1886 my $rpcenv = PVE
::RPCEnvironment
::get
();
1888 my $authuser = $rpcenv->get_user();
1890 my $node = extract_param
($param, 'node');
1892 my $vmid = extract_param
($param, 'vmid');
1894 my $digest = extract_param
($param, 'digest');
1896 my $disk = extract_param
($param, 'disk');
1898 my $sizestr = extract_param
($param, 'size');
1900 my $skiplock = extract_param
($param, 'skiplock');
1901 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1902 if $skiplock && $authuser ne 'root@pam';
1904 my $storecfg = PVE
::Storage
::config
();
1906 my $updatefn = sub {
1908 my $conf = PVE
::QemuServer
::load_config
($vmid);
1910 die "checksum missmatch (file change by other user?)\n"
1911 if $digest && $digest ne $conf->{digest
};
1912 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1914 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1916 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1918 my $volid = $drive->{file
};
1920 die "disk '$disk' has no associated volume\n" if !$volid;
1922 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1924 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1926 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1928 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1930 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1931 my ($ext, $newsize, $unit) = ($1, $2, $4);
1934 $newsize = $newsize * 1024;
1935 } elsif ($unit eq 'M') {
1936 $newsize = $newsize * 1024 * 1024;
1937 } elsif ($unit eq 'G') {
1938 $newsize = $newsize * 1024 * 1024 * 1024;
1939 } elsif ($unit eq 'T') {
1940 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1943 $newsize += $size if $ext;
1944 $newsize = int($newsize);
1946 die "unable to skrink disk size\n" if $newsize < $size;
1948 return if $size == $newsize;
1950 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1952 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1954 $drive->{size
} = $newsize;
1955 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1957 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1960 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1964 __PACKAGE__-
>register_method({
1965 name
=> 'snapshot_list',
1966 path
=> '{vmid}/snapshot',
1968 description
=> "List all snapshots.",
1970 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1973 protected
=> 1, # qemu pid files are only readable by root
1975 additionalProperties
=> 0,
1977 vmid
=> get_standard_option
('pve-vmid'),
1978 node
=> get_standard_option
('pve-node'),
1987 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1992 my $vmid = $param->{vmid
};
1994 my $conf = PVE
::QemuServer
::load_config
($vmid);
1995 my $snaphash = $conf->{snapshots
} || {};
1999 foreach my $name (keys %$snaphash) {
2000 my $d = $snaphash->{$name};
2003 snaptime
=> $d->{snaptime
} || 0,
2004 vmstate
=> $d->{vmstate
} ?
1 : 0,
2005 description
=> $d->{description
} || '',
2007 $item->{parent
} = $d->{parent
} if $d->{parent
};
2008 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2012 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2013 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2014 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2016 push @$res, $current;
2021 __PACKAGE__-
>register_method({
2023 path
=> '{vmid}/snapshot',
2027 description
=> "Snapshot a VM.",
2029 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2032 additionalProperties
=> 0,
2034 node
=> get_standard_option
('pve-node'),
2035 vmid
=> get_standard_option
('pve-vmid'),
2036 snapname
=> get_standard_option
('pve-snapshot-name'),
2040 description
=> "Save the vmstate",
2045 description
=> "Freeze the filesystem",
2050 description
=> "A textual description or comment.",
2056 description
=> "the task ID.",
2061 my $rpcenv = PVE
::RPCEnvironment
::get
();
2063 my $authuser = $rpcenv->get_user();
2065 my $node = extract_param
($param, 'node');
2067 my $vmid = extract_param
($param, 'vmid');
2069 my $snapname = extract_param
($param, 'snapname');
2071 die "unable to use snapshot name 'current' (reserved name)\n"
2072 if $snapname eq 'current';
2075 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2076 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2077 $param->{freezefs
}, $param->{description
});
2080 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2083 __PACKAGE__-
>register_method({
2084 name
=> 'snapshot_cmd_idx',
2085 path
=> '{vmid}/snapshot/{snapname}',
2092 additionalProperties
=> 0,
2094 vmid
=> get_standard_option
('pve-vmid'),
2095 node
=> get_standard_option
('pve-node'),
2096 snapname
=> get_standard_option
('pve-snapshot-name'),
2105 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2112 push @$res, { cmd
=> 'rollback' };
2113 push @$res, { cmd
=> 'config' };
2118 __PACKAGE__-
>register_method({
2119 name
=> 'update_snapshot_config',
2120 path
=> '{vmid}/snapshot/{snapname}/config',
2124 description
=> "Update snapshot metadata.",
2126 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2129 additionalProperties
=> 0,
2131 node
=> get_standard_option
('pve-node'),
2132 vmid
=> get_standard_option
('pve-vmid'),
2133 snapname
=> get_standard_option
('pve-snapshot-name'),
2137 description
=> "A textual description or comment.",
2141 returns
=> { type
=> 'null' },
2145 my $rpcenv = PVE
::RPCEnvironment
::get
();
2147 my $authuser = $rpcenv->get_user();
2149 my $vmid = extract_param
($param, 'vmid');
2151 my $snapname = extract_param
($param, 'snapname');
2153 return undef if !defined($param->{description
});
2155 my $updatefn = sub {
2157 my $conf = PVE
::QemuServer
::load_config
($vmid);
2159 PVE
::QemuServer
::check_lock
($conf);
2161 my $snap = $conf->{snapshots
}->{$snapname};
2163 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2165 $snap->{description
} = $param->{description
} if defined($param->{description
});
2167 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2170 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2175 __PACKAGE__-
>register_method({
2176 name
=> 'get_snapshot_config',
2177 path
=> '{vmid}/snapshot/{snapname}/config',
2180 description
=> "Get snapshot configuration",
2182 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2185 additionalProperties
=> 0,
2187 node
=> get_standard_option
('pve-node'),
2188 vmid
=> get_standard_option
('pve-vmid'),
2189 snapname
=> get_standard_option
('pve-snapshot-name'),
2192 returns
=> { type
=> "object" },
2196 my $rpcenv = PVE
::RPCEnvironment
::get
();
2198 my $authuser = $rpcenv->get_user();
2200 my $vmid = extract_param
($param, 'vmid');
2202 my $snapname = extract_param
($param, 'snapname');
2204 my $conf = PVE
::QemuServer
::load_config
($vmid);
2206 my $snap = $conf->{snapshots
}->{$snapname};
2208 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2213 __PACKAGE__-
>register_method({
2215 path
=> '{vmid}/snapshot/{snapname}/rollback',
2219 description
=> "Rollback VM state to specified snapshot.",
2221 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2224 additionalProperties
=> 0,
2226 node
=> get_standard_option
('pve-node'),
2227 vmid
=> get_standard_option
('pve-vmid'),
2228 snapname
=> get_standard_option
('pve-snapshot-name'),
2233 description
=> "the task ID.",
2238 my $rpcenv = PVE
::RPCEnvironment
::get
();
2240 my $authuser = $rpcenv->get_user();
2242 my $node = extract_param
($param, 'node');
2244 my $vmid = extract_param
($param, 'vmid');
2246 my $snapname = extract_param
($param, 'snapname');
2249 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2250 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2253 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2256 __PACKAGE__-
>register_method({
2257 name
=> 'delsnapshot',
2258 path
=> '{vmid}/snapshot/{snapname}',
2262 description
=> "Delete a VM 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'),
2275 description
=> "For removal from config file, even if removing disk snapshots fails.",
2281 description
=> "the task ID.",
2286 my $rpcenv = PVE
::RPCEnvironment
::get
();
2288 my $authuser = $rpcenv->get_user();
2290 my $node = extract_param
($param, 'node');
2292 my $vmid = extract_param
($param, 'vmid');
2294 my $snapname = extract_param
($param, 'snapname');
2297 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2298 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2301 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);