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') {
75 $res->{$ds} = $settings->{$ds};
76 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
77 my ($storeid, $size) = ($2 || $default_storage, $3);
78 die "no storage ID specified (and no default storage)\n" if !$storeid;
79 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
80 my $fmt = $disk->{format
} || $defformat;
81 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
82 $fmt, undef, $size*1024*1024);
83 $disk->{file
} = $volid;
84 $disk->{size
} = $size*1024*1024*1024;
85 push @$vollist, $volid;
86 delete $disk->{format
}; # no longer needed
87 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
90 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
92 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
94 my $foundvolid = undef;
97 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
98 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
100 PVE
::Storage
::foreach_volid
($dl, sub {
102 if($volumeid eq $volid) {
109 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
111 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
112 $disk->{size
} = $size;
113 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
117 # free allocated images on error
119 syslog
('err', "VM $vmid creating disks failed");
120 foreach my $volid (@$vollist) {
121 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
127 # modify vm config if everything went well
128 foreach my $ds (keys %$res) {
129 $conf->{$ds} = $res->{$ds};
135 my $check_vm_modify_config_perm = sub {
136 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
138 return 1 if $authuser eq 'root@pam';
140 foreach my $opt (@$key_list) {
141 # disk checks need to be done somewhere else
142 next if PVE
::QemuServer
::valid_drivename
($opt);
144 if ($opt eq 'sockets' || $opt eq 'cores' ||
145 $opt eq 'cpu' || $opt eq 'smp' ||
146 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
147 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
148 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
149 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
150 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
151 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
152 } elsif ($opt eq 'args' || $opt eq 'lock') {
153 die "only root can set '$opt' config\n";
154 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
155 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
156 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
157 } elsif ($opt =~ m/^net\d+$/) {
158 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
160 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
167 __PACKAGE__-
>register_method({
171 description
=> "Virtual machine index (per node).",
173 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
177 protected
=> 1, # qemu pid files are only readable by root
179 additionalProperties
=> 0,
181 node
=> get_standard_option
('pve-node'),
190 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
195 my $rpcenv = PVE
::RPCEnvironment
::get
();
196 my $authuser = $rpcenv->get_user();
198 my $vmstatus = PVE
::QemuServer
::vmstatus
();
201 foreach my $vmid (keys %$vmstatus) {
202 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
204 my $data = $vmstatus->{$vmid};
205 $data->{vmid
} = $vmid;
212 __PACKAGE__-
>register_method({
216 description
=> "Create or restore a virtual machine.",
218 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.",
220 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
221 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
227 additionalProperties
=> 0,
228 properties
=> PVE
::QemuServer
::json_config_properties
(
230 node
=> get_standard_option
('pve-node'),
231 vmid
=> get_standard_option
('pve-vmid'),
233 description
=> "The backup file.",
238 storage
=> get_standard_option
('pve-storage-id', {
239 description
=> "Default storage.",
245 description
=> "Allow to overwrite existing VM.",
246 requires
=> 'archive',
251 description
=> "Assign a unique random ethernet address.",
252 requires
=> 'archive',
256 type
=> 'string', format
=> 'pve-poolid',
257 description
=> "Add the VM to the specified pool.",
267 my $rpcenv = PVE
::RPCEnvironment
::get
();
269 my $authuser = $rpcenv->get_user();
271 my $node = extract_param
($param, 'node');
273 my $vmid = extract_param
($param, 'vmid');
275 my $archive = extract_param
($param, 'archive');
277 my $storage = extract_param
($param, 'storage');
279 my $force = extract_param
($param, 'force');
281 my $unique = extract_param
($param, 'unique');
283 my $pool = extract_param
($param, 'pool');
285 my $filename = PVE
::QemuServer
::config_file
($vmid);
287 my $storecfg = PVE
::Storage
::config
();
289 PVE
::Cluster
::check_cfs_quorum
();
291 if (defined($pool)) {
292 $rpcenv->check_pool_exist($pool);
295 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
296 if defined($storage);
299 &$resolve_cdrom_alias($param);
301 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
303 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
305 foreach my $opt (keys %$param) {
306 if (PVE
::QemuServer
::valid_drivename
($opt)) {
307 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
308 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
310 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
311 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
315 PVE
::QemuServer
::add_random_macs
($param);
317 my $keystr = join(' ', keys %$param);
318 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
320 if ($archive eq '-') {
321 die "pipe requires cli environment\n"
322 if $rpcenv->{type
} ne 'cli';
324 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
326 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
327 if PVE
::Storage
::parse_volume_id
($archive, 1);
329 die "can't find archive file '$archive'\n" if !($path && -f
$path);
334 my $addVMtoPoolFn = sub {
335 my $usercfg = cfs_read_file
("user.cfg");
336 if (my $data = $usercfg->{pools
}->{$pool}) {
337 $data->{vms
}->{$vmid} = 1;
338 $usercfg->{vms
}->{$vmid} = $pool;
339 cfs_write_file
("user.cfg", $usercfg);
343 my $restorefn = sub {
346 die "unable to restore vm $vmid: config file already exists\n"
349 die "unable to restore vm $vmid: vm is running\n"
350 if PVE
::QemuServer
::check_running
($vmid);
352 # destroy existing data - keep empty config
353 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, 1);
357 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
360 unique
=> $unique });
362 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
365 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
371 die "unable to create vm $vmid: config file already exists\n"
382 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
384 # try to be smart about bootdisk
385 my @disks = PVE
::QemuServer
::disknames
();
387 foreach my $ds (reverse @disks) {
388 next if !$conf->{$ds};
389 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
390 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
394 if (!$conf->{bootdisk
} && $firstdisk) {
395 $conf->{bootdisk
} = $firstdisk;
398 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
404 foreach my $volid (@$vollist) {
405 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
408 die "create failed - $err";
411 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
414 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
417 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
420 __PACKAGE__-
>register_method({
425 description
=> "Directory index",
430 additionalProperties
=> 0,
432 node
=> get_standard_option
('pve-node'),
433 vmid
=> get_standard_option
('pve-vmid'),
441 subdir
=> { type
=> 'string' },
444 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
450 { subdir
=> 'config' },
451 { subdir
=> 'status' },
452 { subdir
=> 'unlink' },
453 { subdir
=> 'vncproxy' },
454 { subdir
=> 'migrate' },
455 { subdir
=> 'resize' },
457 { subdir
=> 'rrddata' },
458 { subdir
=> 'monitor' },
459 { subdir
=> 'snapshot' },
465 __PACKAGE__-
>register_method({
467 path
=> '{vmid}/rrd',
469 protected
=> 1, # fixme: can we avoid that?
471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
473 description
=> "Read VM RRD statistics (returns PNG)",
475 additionalProperties
=> 0,
477 node
=> get_standard_option
('pve-node'),
478 vmid
=> get_standard_option
('pve-vmid'),
480 description
=> "Specify the time frame you are interested in.",
482 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
485 description
=> "The list of datasources you want to display.",
486 type
=> 'string', format
=> 'pve-configid-list',
489 description
=> "The RRD consolidation function",
491 enum
=> [ 'AVERAGE', 'MAX' ],
499 filename
=> { type
=> 'string' },
505 return PVE
::Cluster
::create_rrd_graph
(
506 "pve2-vm/$param->{vmid}", $param->{timeframe
},
507 $param->{ds
}, $param->{cf
});
511 __PACKAGE__-
>register_method({
513 path
=> '{vmid}/rrddata',
515 protected
=> 1, # fixme: can we avoid that?
517 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
519 description
=> "Read VM RRD statistics",
521 additionalProperties
=> 0,
523 node
=> get_standard_option
('pve-node'),
524 vmid
=> get_standard_option
('pve-vmid'),
526 description
=> "Specify the time frame you are interested in.",
528 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
531 description
=> "The RRD consolidation function",
533 enum
=> [ 'AVERAGE', 'MAX' ],
548 return PVE
::Cluster
::create_rrd_data
(
549 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
553 __PACKAGE__-
>register_method({
555 path
=> '{vmid}/config',
558 description
=> "Get virtual machine configuration.",
560 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
563 additionalProperties
=> 0,
565 node
=> get_standard_option
('pve-node'),
566 vmid
=> get_standard_option
('pve-vmid'),
574 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
581 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
583 delete $conf->{snapshots
};
588 my $vm_is_volid_owner = sub {
589 my ($storecfg, $vmid, $volid) =@_;
591 if ($volid !~ m
|^/|) {
593 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
594 if ($owner && ($owner == $vmid)) {
602 my $test_deallocate_drive = sub {
603 my ($storecfg, $vmid, $key, $drive, $force) = @_;
605 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
606 my $volid = $drive->{file
};
607 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
608 if ($force || $key =~ m/^unused/) {
609 my $sid = PVE
::Storage
::parse_volume_id
($volid);
618 my $delete_drive = sub {
619 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
621 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
622 my $volid = $drive->{file
};
623 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
624 if ($force || $key =~ m/^unused/) {
625 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
628 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
633 delete $conf->{$key};
636 my $vmconfig_delete_option = sub {
637 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
639 return if !defined($conf->{$opt});
641 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
644 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
646 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
647 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
648 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
652 die "error hot-unplug $opt" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
655 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
656 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
658 delete $conf->{$opt};
661 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
664 my $safe_num_ne = sub {
667 return 0 if !defined($a) && !defined($b);
668 return 1 if !defined($a);
669 return 1 if !defined($b);
674 my $vmconfig_update_disk = sub {
675 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
677 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
679 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
680 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
682 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
687 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
689 my $media = $drive->{media
} || 'disk';
690 my $oldmedia = $old_drive->{media
} || 'disk';
691 die "unable to change media type\n" if $media ne $oldmedia;
693 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
694 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
696 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
697 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
700 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
701 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
702 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
703 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
704 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
705 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
706 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
707 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
708 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
709 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
714 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
715 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
717 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
718 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
720 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
722 if (PVE
::QemuServer
::check_running
($vmid)) {
723 if ($drive->{file
} eq 'none') {
724 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
726 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
727 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
728 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
732 } else { # hotplug new disks
734 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
738 my $vmconfig_update_net = sub {
739 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
742 #if online update, then unplug first
743 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
746 $conf->{$opt} = $value;
747 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
748 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
750 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
752 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
755 my $vm_config_perm_list = [
765 __PACKAGE__-
>register_method({
767 path
=> '{vmid}/config',
771 description
=> "Set virtual machine options.",
773 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
776 additionalProperties
=> 0,
777 properties
=> PVE
::QemuServer
::json_config_properties
(
779 node
=> get_standard_option
('pve-node'),
780 vmid
=> get_standard_option
('pve-vmid'),
781 skiplock
=> get_standard_option
('skiplock'),
783 type
=> 'string', format
=> 'pve-configid-list',
784 description
=> "A list of settings you want to delete.",
789 description
=> $opt_force_description,
791 requires
=> 'delete',
795 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
801 returns
=> { type
=> 'null'},
805 my $rpcenv = PVE
::RPCEnvironment
::get
();
807 my $authuser = $rpcenv->get_user();
809 my $node = extract_param
($param, 'node');
811 my $vmid = extract_param
($param, 'vmid');
813 my $digest = extract_param
($param, 'digest');
815 my @paramarr = (); # used for log message
816 foreach my $key (keys %$param) {
817 push @paramarr, "-$key", $param->{$key};
820 my $skiplock = extract_param
($param, 'skiplock');
821 raise_param_exc
({ skiplock
=> "Only root may use this option." })
822 if $skiplock && $authuser ne 'root@pam';
824 my $delete_str = extract_param
($param, 'delete');
826 my $force = extract_param
($param, 'force');
828 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
830 my $storecfg = PVE
::Storage
::config
();
832 my $defaults = PVE
::QemuServer
::load_defaults
();
834 &$resolve_cdrom_alias($param);
836 # now try to verify all parameters
839 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
840 $opt = 'ide2' if $opt eq 'cdrom';
841 raise_param_exc
({ delete => "you can't use '-$opt' and " .
842 "-delete $opt' at the same time" })
843 if defined($param->{$opt});
845 if (!PVE
::QemuServer
::option_exists
($opt)) {
846 raise_param_exc
({ delete => "unknown option '$opt'" });
852 foreach my $opt (keys %$param) {
853 if (PVE
::QemuServer
::valid_drivename
($opt)) {
855 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
856 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
857 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
858 } elsif ($opt =~ m/^net(\d+)$/) {
860 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
861 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
865 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
867 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
869 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
873 my $conf = PVE
::QemuServer
::load_config
($vmid);
875 die "checksum missmatch (file change by other user?)\n"
876 if $digest && $digest ne $conf->{digest
};
878 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
880 if ($param->{memory
} || defined($param->{balloon
})) {
881 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
882 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
884 die "balloon value too large (must be smaller than assigned memory)\n"
885 if $balloon > $maxmem;
888 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
890 foreach my $opt (@delete) { # delete
891 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
892 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
895 my $running = PVE
::QemuServer
::check_running
($vmid);
897 foreach my $opt (keys %$param) { # add/change
899 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
901 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
903 if (PVE
::QemuServer
::valid_drivename
($opt)) {
905 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
906 $opt, $param->{$opt}, $force);
908 } elsif ($opt =~ m/^net(\d+)$/) { #nics
910 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
911 $opt, $param->{$opt});
915 $conf->{$opt} = $param->{$opt};
916 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
920 # allow manual ballooning if shares is set to zero
921 if ($running && defined($param->{balloon
}) &&
922 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
923 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
924 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
929 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
935 __PACKAGE__-
>register_method({
936 name
=> 'destroy_vm',
941 description
=> "Destroy the vm (also delete all used/owned volumes).",
943 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
946 additionalProperties
=> 0,
948 node
=> get_standard_option
('pve-node'),
949 vmid
=> get_standard_option
('pve-vmid'),
950 skiplock
=> get_standard_option
('skiplock'),
959 my $rpcenv = PVE
::RPCEnvironment
::get
();
961 my $authuser = $rpcenv->get_user();
963 my $vmid = $param->{vmid
};
965 my $skiplock = $param->{skiplock
};
966 raise_param_exc
({ skiplock
=> "Only root may use this option." })
967 if $skiplock && $authuser ne 'root@pam';
970 my $conf = PVE
::QemuServer
::load_config
($vmid);
972 my $storecfg = PVE
::Storage
::config
();
974 my $delVMfromPoolFn = sub {
975 my $usercfg = cfs_read_file
("user.cfg");
976 if (my $pool = $usercfg->{vms
}->{$vmid}) {
977 if (my $data = $usercfg->{pools
}->{$pool}) {
978 delete $data->{vms
}->{$vmid};
979 delete $usercfg->{vms
}->{$vmid};
980 cfs_write_file
("user.cfg", $usercfg);
988 syslog
('info', "destroy VM $vmid: $upid\n");
990 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
992 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
995 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
998 __PACKAGE__-
>register_method({
1000 path
=> '{vmid}/unlink',
1004 description
=> "Unlink/delete disk images.",
1006 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1009 additionalProperties
=> 0,
1011 node
=> get_standard_option
('pve-node'),
1012 vmid
=> get_standard_option
('pve-vmid'),
1014 type
=> 'string', format
=> 'pve-configid-list',
1015 description
=> "A list of disk IDs you want to delete.",
1019 description
=> $opt_force_description,
1024 returns
=> { type
=> 'null'},
1028 $param->{delete} = extract_param
($param, 'idlist');
1030 __PACKAGE__-
>update_vm($param);
1037 __PACKAGE__-
>register_method({
1039 path
=> '{vmid}/vncproxy',
1043 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1045 description
=> "Creates a TCP VNC proxy connections.",
1047 additionalProperties
=> 0,
1049 node
=> get_standard_option
('pve-node'),
1050 vmid
=> get_standard_option
('pve-vmid'),
1054 additionalProperties
=> 0,
1056 user
=> { type
=> 'string' },
1057 ticket
=> { type
=> 'string' },
1058 cert
=> { type
=> 'string' },
1059 port
=> { type
=> 'integer' },
1060 upid
=> { type
=> 'string' },
1066 my $rpcenv = PVE
::RPCEnvironment
::get
();
1068 my $authuser = $rpcenv->get_user();
1070 my $vmid = $param->{vmid
};
1071 my $node = $param->{node
};
1073 my $authpath = "/vms/$vmid";
1075 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1077 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1080 my $port = PVE
::Tools
::next_vnc_port
();
1084 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1085 $remip = PVE
::Cluster
::remote_node_ip
($node);
1088 # NOTE: kvm VNC traffic is already TLS encrypted
1089 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1096 syslog
('info', "starting vnc proxy $upid\n");
1098 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1100 my $qmstr = join(' ', @$qmcmd);
1102 # also redirect stderr (else we get RFB protocol errors)
1103 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1105 PVE
::Tools
::run_command
($cmd);
1110 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1112 PVE
::Tools
::wait_for_vnc_port
($port);
1123 __PACKAGE__-
>register_method({
1125 path
=> '{vmid}/status',
1128 description
=> "Directory index",
1133 additionalProperties
=> 0,
1135 node
=> get_standard_option
('pve-node'),
1136 vmid
=> get_standard_option
('pve-vmid'),
1144 subdir
=> { type
=> 'string' },
1147 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1153 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1156 { subdir
=> 'current' },
1157 { subdir
=> 'start' },
1158 { subdir
=> 'stop' },
1164 my $vm_is_ha_managed = sub {
1167 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1168 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1174 __PACKAGE__-
>register_method({
1175 name
=> 'vm_status',
1176 path
=> '{vmid}/status/current',
1179 protected
=> 1, # qemu pid files are only readable by root
1180 description
=> "Get virtual machine status.",
1182 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1185 additionalProperties
=> 0,
1187 node
=> get_standard_option
('pve-node'),
1188 vmid
=> get_standard_option
('pve-vmid'),
1191 returns
=> { type
=> 'object' },
1196 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1198 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1199 my $status = $vmstatus->{$param->{vmid
}};
1201 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1206 __PACKAGE__-
>register_method({
1208 path
=> '{vmid}/status/start',
1212 description
=> "Start virtual machine.",
1214 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1217 additionalProperties
=> 0,
1219 node
=> get_standard_option
('pve-node'),
1220 vmid
=> get_standard_option
('pve-vmid'),
1221 skiplock
=> get_standard_option
('skiplock'),
1222 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1223 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1233 my $rpcenv = PVE
::RPCEnvironment
::get
();
1235 my $authuser = $rpcenv->get_user();
1237 my $node = extract_param
($param, 'node');
1239 my $vmid = extract_param
($param, 'vmid');
1241 my $stateuri = extract_param
($param, 'stateuri');
1242 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1243 if $stateuri && $authuser ne 'root@pam';
1245 my $skiplock = extract_param
($param, 'skiplock');
1246 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1247 if $skiplock && $authuser ne 'root@pam';
1249 my $migratedfrom = extract_param
($param, 'migratedfrom');
1250 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1251 if $migratedfrom && $authuser ne 'root@pam';
1253 my $storecfg = PVE
::Storage
::config
();
1255 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1256 $rpcenv->{type
} ne 'ha') {
1261 my $service = "pvevm:$vmid";
1263 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1265 print "Executing HA start for VM $vmid\n";
1267 PVE
::Tools
::run_command
($cmd);
1272 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1279 syslog
('info', "start VM $vmid: $upid\n");
1281 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1286 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1290 __PACKAGE__-
>register_method({
1292 path
=> '{vmid}/status/stop',
1296 description
=> "Stop virtual machine.",
1298 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1301 additionalProperties
=> 0,
1303 node
=> get_standard_option
('pve-node'),
1304 vmid
=> get_standard_option
('pve-vmid'),
1305 skiplock
=> get_standard_option
('skiplock'),
1306 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1308 description
=> "Wait maximal timeout seconds.",
1314 description
=> "Do not decativate storage volumes.",
1327 my $rpcenv = PVE
::RPCEnvironment
::get
();
1329 my $authuser = $rpcenv->get_user();
1331 my $node = extract_param
($param, 'node');
1333 my $vmid = extract_param
($param, 'vmid');
1335 my $skiplock = extract_param
($param, 'skiplock');
1336 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1337 if $skiplock && $authuser ne 'root@pam';
1339 my $keepActive = extract_param
($param, 'keepActive');
1340 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1341 if $keepActive && $authuser ne 'root@pam';
1343 my $migratedfrom = extract_param
($param, 'migratedfrom');
1344 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1345 if $migratedfrom && $authuser ne 'root@pam';
1348 my $storecfg = PVE
::Storage
::config
();
1350 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1355 my $service = "pvevm:$vmid";
1357 my $cmd = ['clusvcadm', '-d', $service];
1359 print "Executing HA stop for VM $vmid\n";
1361 PVE
::Tools
::run_command
($cmd);
1366 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1372 syslog
('info', "stop VM $vmid: $upid\n");
1374 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1375 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1380 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1384 __PACKAGE__-
>register_method({
1386 path
=> '{vmid}/status/reset',
1390 description
=> "Reset virtual machine.",
1392 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1395 additionalProperties
=> 0,
1397 node
=> get_standard_option
('pve-node'),
1398 vmid
=> get_standard_option
('pve-vmid'),
1399 skiplock
=> get_standard_option
('skiplock'),
1408 my $rpcenv = PVE
::RPCEnvironment
::get
();
1410 my $authuser = $rpcenv->get_user();
1412 my $node = extract_param
($param, 'node');
1414 my $vmid = extract_param
($param, 'vmid');
1416 my $skiplock = extract_param
($param, 'skiplock');
1417 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1418 if $skiplock && $authuser ne 'root@pam';
1420 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1425 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1430 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1433 __PACKAGE__-
>register_method({
1434 name
=> 'vm_shutdown',
1435 path
=> '{vmid}/status/shutdown',
1439 description
=> "Shutdown virtual machine.",
1441 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1444 additionalProperties
=> 0,
1446 node
=> get_standard_option
('pve-node'),
1447 vmid
=> get_standard_option
('pve-vmid'),
1448 skiplock
=> get_standard_option
('skiplock'),
1450 description
=> "Wait maximal timeout seconds.",
1456 description
=> "Make sure the VM stops.",
1462 description
=> "Do not decativate storage volumes.",
1475 my $rpcenv = PVE
::RPCEnvironment
::get
();
1477 my $authuser = $rpcenv->get_user();
1479 my $node = extract_param
($param, 'node');
1481 my $vmid = extract_param
($param, 'vmid');
1483 my $skiplock = extract_param
($param, 'skiplock');
1484 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1485 if $skiplock && $authuser ne 'root@pam';
1487 my $keepActive = extract_param
($param, 'keepActive');
1488 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1489 if $keepActive && $authuser ne 'root@pam';
1491 my $storecfg = PVE
::Storage
::config
();
1496 syslog
('info', "shutdown VM $vmid: $upid\n");
1498 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1499 1, $param->{forceStop
}, $keepActive);
1504 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1507 __PACKAGE__-
>register_method({
1508 name
=> 'vm_suspend',
1509 path
=> '{vmid}/status/suspend',
1513 description
=> "Suspend virtual machine.",
1515 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1518 additionalProperties
=> 0,
1520 node
=> get_standard_option
('pve-node'),
1521 vmid
=> get_standard_option
('pve-vmid'),
1522 skiplock
=> get_standard_option
('skiplock'),
1531 my $rpcenv = PVE
::RPCEnvironment
::get
();
1533 my $authuser = $rpcenv->get_user();
1535 my $node = extract_param
($param, 'node');
1537 my $vmid = extract_param
($param, 'vmid');
1539 my $skiplock = extract_param
($param, 'skiplock');
1540 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1541 if $skiplock && $authuser ne 'root@pam';
1543 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1548 syslog
('info', "suspend VM $vmid: $upid\n");
1550 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1555 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1558 __PACKAGE__-
>register_method({
1559 name
=> 'vm_resume',
1560 path
=> '{vmid}/status/resume',
1564 description
=> "Resume virtual machine.",
1566 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid'),
1573 skiplock
=> get_standard_option
('skiplock'),
1582 my $rpcenv = PVE
::RPCEnvironment
::get
();
1584 my $authuser = $rpcenv->get_user();
1586 my $node = extract_param
($param, 'node');
1588 my $vmid = extract_param
($param, 'vmid');
1590 my $skiplock = extract_param
($param, 'skiplock');
1591 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1592 if $skiplock && $authuser ne 'root@pam';
1594 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1599 syslog
('info', "resume VM $vmid: $upid\n");
1601 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1606 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1609 __PACKAGE__-
>register_method({
1610 name
=> 'vm_sendkey',
1611 path
=> '{vmid}/sendkey',
1615 description
=> "Send key event to virtual machine.",
1617 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1620 additionalProperties
=> 0,
1622 node
=> get_standard_option
('pve-node'),
1623 vmid
=> get_standard_option
('pve-vmid'),
1624 skiplock
=> get_standard_option
('skiplock'),
1626 description
=> "The key (qemu monitor encoding).",
1631 returns
=> { type
=> 'null'},
1635 my $rpcenv = PVE
::RPCEnvironment
::get
();
1637 my $authuser = $rpcenv->get_user();
1639 my $node = extract_param
($param, 'node');
1641 my $vmid = extract_param
($param, 'vmid');
1643 my $skiplock = extract_param
($param, 'skiplock');
1644 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1645 if $skiplock && $authuser ne 'root@pam';
1647 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1652 __PACKAGE__-
>register_method({
1653 name
=> 'vm_feature',
1654 path
=> '{vmid}/feature',
1658 description
=> "Check if feature for virtual machine is available.",
1660 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1663 additionalProperties
=> 0,
1665 node
=> get_standard_option
('pve-node'),
1666 vmid
=> get_standard_option
('pve-vmid'),
1668 description
=> "Feature to check.",
1670 enum
=> [ 'snapshot', 'clone' ],
1672 snapname
=> get_standard_option
('pve-snapshot-name', {
1684 my $node = extract_param
($param, 'node');
1686 my $vmid = extract_param
($param, 'vmid');
1688 my $snapname = extract_param
($param, 'snapname');
1690 my $feature = extract_param
($param, 'feature');
1692 my $running = PVE
::QemuServer
::check_running
($vmid);
1694 my $conf = PVE
::QemuServer
::load_config
($vmid);
1697 my $snap = $conf->{snapshots
}->{$snapname};
1698 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1701 my $storecfg = PVE
::Storage
::config
();
1703 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1704 my $res = $hasfeature ?
1 : 0 ;
1708 __PACKAGE__-
>register_method({
1709 name
=> 'migrate_vm',
1710 path
=> '{vmid}/migrate',
1714 description
=> "Migrate virtual machine. Creates a new migration task.",
1716 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1719 additionalProperties
=> 0,
1721 node
=> get_standard_option
('pve-node'),
1722 vmid
=> get_standard_option
('pve-vmid'),
1723 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1726 description
=> "Use online/live migration.",
1731 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1738 description
=> "the task ID.",
1743 my $rpcenv = PVE
::RPCEnvironment
::get
();
1745 my $authuser = $rpcenv->get_user();
1747 my $target = extract_param
($param, 'target');
1749 my $localnode = PVE
::INotify
::nodename
();
1750 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1752 PVE
::Cluster
::check_cfs_quorum
();
1754 PVE
::Cluster
::check_node_exists
($target);
1756 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1758 my $vmid = extract_param
($param, 'vmid');
1760 raise_param_exc
({ force
=> "Only root may use this option." })
1761 if $param->{force
} && $authuser ne 'root@pam';
1764 my $conf = PVE
::QemuServer
::load_config
($vmid);
1766 # try to detect errors early
1768 PVE
::QemuServer
::check_lock
($conf);
1770 if (PVE
::QemuServer
::check_running
($vmid)) {
1771 die "cant migrate running VM without --online\n"
1772 if !$param->{online
};
1775 my $storecfg = PVE
::Storage
::config
();
1776 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1778 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1783 my $service = "pvevm:$vmid";
1785 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1787 print "Executing HA migrate for VM $vmid to node $target\n";
1789 PVE
::Tools
::run_command
($cmd);
1794 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1801 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1804 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1809 __PACKAGE__-
>register_method({
1811 path
=> '{vmid}/monitor',
1815 description
=> "Execute Qemu monitor commands.",
1817 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1820 additionalProperties
=> 0,
1822 node
=> get_standard_option
('pve-node'),
1823 vmid
=> get_standard_option
('pve-vmid'),
1826 description
=> "The monitor command.",
1830 returns
=> { type
=> 'string'},
1834 my $vmid = $param->{vmid
};
1836 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1840 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1842 $res = "ERROR: $@" if $@;
1847 __PACKAGE__-
>register_method({
1848 name
=> 'resize_vm',
1849 path
=> '{vmid}/resize',
1853 description
=> "Extend volume size.",
1855 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1858 additionalProperties
=> 0,
1860 node
=> get_standard_option
('pve-node'),
1861 vmid
=> get_standard_option
('pve-vmid'),
1862 skiplock
=> get_standard_option
('skiplock'),
1865 description
=> "The disk you want to resize.",
1866 enum
=> [PVE
::QemuServer
::disknames
()],
1870 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1871 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.",
1875 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1881 returns
=> { type
=> 'null'},
1885 my $rpcenv = PVE
::RPCEnvironment
::get
();
1887 my $authuser = $rpcenv->get_user();
1889 my $node = extract_param
($param, 'node');
1891 my $vmid = extract_param
($param, 'vmid');
1893 my $digest = extract_param
($param, 'digest');
1895 my $disk = extract_param
($param, 'disk');
1897 my $sizestr = extract_param
($param, 'size');
1899 my $skiplock = extract_param
($param, 'skiplock');
1900 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1901 if $skiplock && $authuser ne 'root@pam';
1903 my $storecfg = PVE
::Storage
::config
();
1905 my $updatefn = sub {
1907 my $conf = PVE
::QemuServer
::load_config
($vmid);
1909 die "checksum missmatch (file change by other user?)\n"
1910 if $digest && $digest ne $conf->{digest
};
1911 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1913 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1915 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1917 my $volid = $drive->{file
};
1919 die "disk '$disk' has no associated volume\n" if !$volid;
1921 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1923 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1925 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1927 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1929 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1930 my ($ext, $newsize, $unit) = ($1, $2, $4);
1933 $newsize = $newsize * 1024;
1934 } elsif ($unit eq 'M') {
1935 $newsize = $newsize * 1024 * 1024;
1936 } elsif ($unit eq 'G') {
1937 $newsize = $newsize * 1024 * 1024 * 1024;
1938 } elsif ($unit eq 'T') {
1939 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1942 $newsize += $size if $ext;
1943 $newsize = int($newsize);
1945 die "unable to skrink disk size\n" if $newsize < $size;
1947 return if $size == $newsize;
1949 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1951 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1953 $drive->{size
} = $newsize;
1954 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1956 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1959 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1963 __PACKAGE__-
>register_method({
1964 name
=> 'snapshot_list',
1965 path
=> '{vmid}/snapshot',
1967 description
=> "List all snapshots.",
1969 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1972 protected
=> 1, # qemu pid files are only readable by root
1974 additionalProperties
=> 0,
1976 vmid
=> get_standard_option
('pve-vmid'),
1977 node
=> get_standard_option
('pve-node'),
1986 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1991 my $vmid = $param->{vmid
};
1993 my $conf = PVE
::QemuServer
::load_config
($vmid);
1994 my $snaphash = $conf->{snapshots
} || {};
1998 foreach my $name (keys %$snaphash) {
1999 my $d = $snaphash->{$name};
2002 snaptime
=> $d->{snaptime
} || 0,
2003 vmstate
=> $d->{vmstate
} ?
1 : 0,
2004 description
=> $d->{description
} || '',
2006 $item->{parent
} = $d->{parent
} if $d->{parent
};
2007 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2011 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2012 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2013 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2015 push @$res, $current;
2020 __PACKAGE__-
>register_method({
2022 path
=> '{vmid}/snapshot',
2026 description
=> "Snapshot a VM.",
2028 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2031 additionalProperties
=> 0,
2033 node
=> get_standard_option
('pve-node'),
2034 vmid
=> get_standard_option
('pve-vmid'),
2035 snapname
=> get_standard_option
('pve-snapshot-name'),
2039 description
=> "Save the vmstate",
2044 description
=> "Freeze the filesystem",
2049 description
=> "A textual description or comment.",
2055 description
=> "the task ID.",
2060 my $rpcenv = PVE
::RPCEnvironment
::get
();
2062 my $authuser = $rpcenv->get_user();
2064 my $node = extract_param
($param, 'node');
2066 my $vmid = extract_param
($param, 'vmid');
2068 my $snapname = extract_param
($param, 'snapname');
2070 die "unable to use snapshot name 'current' (reserved name)\n"
2071 if $snapname eq 'current';
2074 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2075 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2076 $param->{freezefs
}, $param->{description
});
2079 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2082 __PACKAGE__-
>register_method({
2083 name
=> 'snapshot_cmd_idx',
2084 path
=> '{vmid}/snapshot/{snapname}',
2091 additionalProperties
=> 0,
2093 vmid
=> get_standard_option
('pve-vmid'),
2094 node
=> get_standard_option
('pve-node'),
2095 snapname
=> get_standard_option
('pve-snapshot-name'),
2104 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2111 push @$res, { cmd
=> 'rollback' };
2112 push @$res, { cmd
=> 'config' };
2117 __PACKAGE__-
>register_method({
2118 name
=> 'update_snapshot_config',
2119 path
=> '{vmid}/snapshot/{snapname}/config',
2123 description
=> "Update snapshot metadata.",
2125 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2128 additionalProperties
=> 0,
2130 node
=> get_standard_option
('pve-node'),
2131 vmid
=> get_standard_option
('pve-vmid'),
2132 snapname
=> get_standard_option
('pve-snapshot-name'),
2136 description
=> "A textual description or comment.",
2140 returns
=> { type
=> 'null' },
2144 my $rpcenv = PVE
::RPCEnvironment
::get
();
2146 my $authuser = $rpcenv->get_user();
2148 my $vmid = extract_param
($param, 'vmid');
2150 my $snapname = extract_param
($param, 'snapname');
2152 return undef if !defined($param->{description
});
2154 my $updatefn = sub {
2156 my $conf = PVE
::QemuServer
::load_config
($vmid);
2158 PVE
::QemuServer
::check_lock
($conf);
2160 my $snap = $conf->{snapshots
}->{$snapname};
2162 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2164 $snap->{description
} = $param->{description
} if defined($param->{description
});
2166 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2169 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2174 __PACKAGE__-
>register_method({
2175 name
=> 'get_snapshot_config',
2176 path
=> '{vmid}/snapshot/{snapname}/config',
2179 description
=> "Get snapshot configuration",
2181 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2184 additionalProperties
=> 0,
2186 node
=> get_standard_option
('pve-node'),
2187 vmid
=> get_standard_option
('pve-vmid'),
2188 snapname
=> get_standard_option
('pve-snapshot-name'),
2191 returns
=> { type
=> "object" },
2195 my $rpcenv = PVE
::RPCEnvironment
::get
();
2197 my $authuser = $rpcenv->get_user();
2199 my $vmid = extract_param
($param, 'vmid');
2201 my $snapname = extract_param
($param, 'snapname');
2203 my $conf = PVE
::QemuServer
::load_config
($vmid);
2205 my $snap = $conf->{snapshots
}->{$snapname};
2207 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2212 __PACKAGE__-
>register_method({
2214 path
=> '{vmid}/snapshot/{snapname}/rollback',
2218 description
=> "Rollback VM state to specified snapshot.",
2220 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2223 additionalProperties
=> 0,
2225 node
=> get_standard_option
('pve-node'),
2226 vmid
=> get_standard_option
('pve-vmid'),
2227 snapname
=> get_standard_option
('pve-snapshot-name'),
2232 description
=> "the task ID.",
2237 my $rpcenv = PVE
::RPCEnvironment
::get
();
2239 my $authuser = $rpcenv->get_user();
2241 my $node = extract_param
($param, 'node');
2243 my $vmid = extract_param
($param, 'vmid');
2245 my $snapname = extract_param
($param, 'snapname');
2248 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2249 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2252 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2255 __PACKAGE__-
>register_method({
2256 name
=> 'delsnapshot',
2257 path
=> '{vmid}/snapshot/{snapname}',
2261 description
=> "Delete a VM snapshot.",
2263 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2266 additionalProperties
=> 0,
2268 node
=> get_standard_option
('pve-node'),
2269 vmid
=> get_standard_option
('pve-vmid'),
2270 snapname
=> get_standard_option
('pve-snapshot-name'),
2274 description
=> "For removal from config file, even if removing disk snapshots fails.",
2280 description
=> "the task ID.",
2285 my $rpcenv = PVE
::RPCEnvironment
::get
();
2287 my $authuser = $rpcenv->get_user();
2289 my $node = extract_param
($param, 'node');
2291 my $vmid = extract_param
($param, 'vmid');
2293 my $snapname = extract_param
($param, 'snapname');
2296 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2297 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2300 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);