1 package PVE
::API2
::Qemu
;
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
9 use PVE
::Tools
qw(extract_param);
10 use PVE
::Exception
qw(raise raise_param_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
16 use PVE
::RPCEnvironment
;
17 use PVE
::AccessControl
;
20 use Data
::Dumper
; # fixme: remove
22 use base
qw(PVE::RESTHandler);
24 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
26 my $resolve_cdrom_alias = sub {
29 if (my $value = $param->{cdrom
}) {
30 $value .= ",media=cdrom" if $value !~ m/media=/;
31 $param->{ide2
} = $value;
32 delete $param->{cdrom
};
37 my $check_storage_access = sub {
38 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
40 PVE
::QemuServer
::foreach_drive
($settings, sub {
41 my ($ds, $drive) = @_;
43 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
45 my $volid = $drive->{file
};
47 if (!$volid || $volid eq 'none') {
49 } elsif ($isCDROM && ($volid eq 'cdrom')) {
50 $rpcenv->check($authuser, "/", ['Sys.Console']);
51 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
52 my ($storeid, $size) = ($2 || $default_storage, $3);
53 die "no storage ID specified (and no default storage)\n" if !$storeid;
54 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
56 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
61 # Note: $pool is only needed when creating a VM, because pool permissions
62 # are automatically inherited if VM already exists inside a pool.
63 my $create_disks = sub {
64 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
69 PVE
::QemuServer
::foreach_drive
($settings, sub {
72 my $volid = $disk->{file
};
74 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
76 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
77 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
78 my ($storeid, $size) = ($2 || $default_storage, $3);
79 die "no storage ID specified (and no default storage)\n" if !$storeid;
80 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
81 my $fmt = $disk->{format
} || $defformat;
82 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
83 $fmt, undef, $size*1024*1024);
84 $disk->{file
} = $volid;
85 $disk->{size
} = $size*1024*1024*1024;
86 push @$vollist, $volid;
87 delete $disk->{format
}; # no longer needed
88 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
91 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
93 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
95 my $foundvolid = undef;
98 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]);
99 my $dl = PVE
::Storage
::vdisk_list
($storecfg, $storeid, undef);
101 PVE
::Storage
::foreach_volid
($dl, sub {
103 if($volumeid eq $volid) {
110 die "image '$path' does not exists\n" if (!(-f
$path || -b
$path || $foundvolid));
112 my ($size) = PVE
::Storage
::volume_size_info
($storecfg, $volid, 1);
113 $disk->{size
} = $size;
114 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
118 # free allocated images on error
120 syslog
('err', "VM $vmid creating disks failed");
121 foreach my $volid (@$vollist) {
122 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
128 # modify vm config if everything went well
129 foreach my $ds (keys %$res) {
130 $conf->{$ds} = $res->{$ds};
136 my $check_vm_modify_config_perm = sub {
137 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
139 return 1 if $authuser eq 'root@pam';
141 foreach my $opt (@$key_list) {
142 # disk checks need to be done somewhere else
143 next if PVE
::QemuServer
::valid_drivename
($opt);
145 if ($opt eq 'sockets' || $opt eq 'cores' ||
146 $opt eq 'cpu' || $opt eq 'smp' ||
147 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
148 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
149 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
150 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
151 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
152 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
153 } elsif ($opt eq 'args' || $opt eq 'lock') {
154 die "only root can set '$opt' config\n";
155 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' ||
156 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
157 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
158 } elsif ($opt =~ m/^net\d+$/) {
159 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
161 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
168 __PACKAGE__-
>register_method({
172 description
=> "Virtual machine index (per node).",
174 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
178 protected
=> 1, # qemu pid files are only readable by root
180 additionalProperties
=> 0,
182 node
=> get_standard_option
('pve-node'),
191 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
196 my $rpcenv = PVE
::RPCEnvironment
::get
();
197 my $authuser = $rpcenv->get_user();
199 my $vmstatus = PVE
::QemuServer
::vmstatus
();
202 foreach my $vmid (keys %$vmstatus) {
203 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
205 my $data = $vmstatus->{$vmid};
206 $data->{vmid
} = $vmid;
213 __PACKAGE__-
>register_method({
217 description
=> "Create or restore a virtual machine.",
219 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
221 [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
222 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
228 additionalProperties
=> 0,
229 properties
=> PVE
::QemuServer
::json_config_properties
(
231 node
=> get_standard_option
('pve-node'),
232 vmid
=> get_standard_option
('pve-vmid'),
234 description
=> "The backup file.",
239 storage
=> get_standard_option
('pve-storage-id', {
240 description
=> "Default storage.",
246 description
=> "Allow to overwrite existing VM.",
247 requires
=> 'archive',
252 description
=> "Assign a unique random ethernet address.",
253 requires
=> 'archive',
257 type
=> 'string', format
=> 'pve-poolid',
258 description
=> "Add the VM to the specified pool.",
268 my $rpcenv = PVE
::RPCEnvironment
::get
();
270 my $authuser = $rpcenv->get_user();
272 my $node = extract_param
($param, 'node');
274 my $vmid = extract_param
($param, 'vmid');
276 my $archive = extract_param
($param, 'archive');
278 my $storage = extract_param
($param, 'storage');
280 my $force = extract_param
($param, 'force');
282 my $unique = extract_param
($param, 'unique');
284 my $pool = extract_param
($param, 'pool');
286 my $filename = PVE
::QemuServer
::config_file
($vmid);
288 my $storecfg = PVE
::Storage
::config
();
290 PVE
::Cluster
::check_cfs_quorum
();
292 if (defined($pool)) {
293 $rpcenv->check_pool_exist($pool);
296 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
297 if defined($storage);
300 &$resolve_cdrom_alias($param);
302 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
304 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
306 foreach my $opt (keys %$param) {
307 if (PVE
::QemuServer
::valid_drivename
($opt)) {
308 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
309 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
311 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
312 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
316 PVE
::QemuServer
::add_random_macs
($param);
318 my $keystr = join(' ', keys %$param);
319 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
321 if ($archive eq '-') {
322 die "pipe requires cli environment\n"
323 if $rpcenv->{type
} ne 'cli';
325 my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
327 PVE
::Storage
::activate_volumes
($storecfg, [ $archive ])
328 if PVE
::Storage
::parse_volume_id
($archive, 1);
330 die "can't find archive file '$archive'\n" if !($path && -f
$path);
335 my $addVMtoPoolFn = sub {
336 my $usercfg = cfs_read_file
("user.cfg");
337 if (my $data = $usercfg->{pools
}->{$pool}) {
338 $data->{vms
}->{$vmid} = 1;
339 $usercfg->{vms
}->{$vmid} = $pool;
340 cfs_write_file
("user.cfg", $usercfg);
344 my $restorefn = sub {
347 die "unable to restore vm $vmid: config file already exists\n"
350 die "unable to restore vm $vmid: vm is running\n"
351 if PVE
::QemuServer
::check_running
($vmid);
355 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
358 unique
=> $unique });
360 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
363 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
369 die "unable to create vm $vmid: config file already exists\n"
380 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
382 # try to be smart about bootdisk
383 my @disks = PVE
::QemuServer
::disknames
();
385 foreach my $ds (reverse @disks) {
386 next if !$conf->{$ds};
387 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
388 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
392 if (!$conf->{bootdisk
} && $firstdisk) {
393 $conf->{bootdisk
} = $firstdisk;
396 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
402 foreach my $volid (@$vollist) {
403 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
406 die "create failed - $err";
409 PVE
::AccessControl
::lock_user_config
($addVMtoPoolFn, "can't add VM to pool") if $pool;
412 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
415 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
418 __PACKAGE__-
>register_method({
423 description
=> "Directory index",
428 additionalProperties
=> 0,
430 node
=> get_standard_option
('pve-node'),
431 vmid
=> get_standard_option
('pve-vmid'),
439 subdir
=> { type
=> 'string' },
442 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
448 { subdir
=> 'config' },
449 { subdir
=> 'status' },
450 { subdir
=> 'unlink' },
451 { subdir
=> 'vncproxy' },
452 { subdir
=> 'migrate' },
453 { subdir
=> 'resize' },
455 { subdir
=> 'rrddata' },
456 { subdir
=> 'monitor' },
457 { subdir
=> 'snapshot' },
463 __PACKAGE__-
>register_method({
465 path
=> '{vmid}/rrd',
467 protected
=> 1, # fixme: can we avoid that?
469 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
471 description
=> "Read VM RRD statistics (returns PNG)",
473 additionalProperties
=> 0,
475 node
=> get_standard_option
('pve-node'),
476 vmid
=> get_standard_option
('pve-vmid'),
478 description
=> "Specify the time frame you are interested in.",
480 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
483 description
=> "The list of datasources you want to display.",
484 type
=> 'string', format
=> 'pve-configid-list',
487 description
=> "The RRD consolidation function",
489 enum
=> [ 'AVERAGE', 'MAX' ],
497 filename
=> { type
=> 'string' },
503 return PVE
::Cluster
::create_rrd_graph
(
504 "pve2-vm/$param->{vmid}", $param->{timeframe
},
505 $param->{ds
}, $param->{cf
});
509 __PACKAGE__-
>register_method({
511 path
=> '{vmid}/rrddata',
513 protected
=> 1, # fixme: can we avoid that?
515 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
517 description
=> "Read VM RRD statistics",
519 additionalProperties
=> 0,
521 node
=> get_standard_option
('pve-node'),
522 vmid
=> get_standard_option
('pve-vmid'),
524 description
=> "Specify the time frame you are interested in.",
526 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
529 description
=> "The RRD consolidation function",
531 enum
=> [ 'AVERAGE', 'MAX' ],
546 return PVE
::Cluster
::create_rrd_data
(
547 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
551 __PACKAGE__-
>register_method({
553 path
=> '{vmid}/config',
556 description
=> "Get virtual machine configuration.",
558 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
561 additionalProperties
=> 0,
563 node
=> get_standard_option
('pve-node'),
564 vmid
=> get_standard_option
('pve-vmid'),
572 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
579 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
581 delete $conf->{snapshots
};
586 my $vm_is_volid_owner = sub {
587 my ($storecfg, $vmid, $volid) =@_;
589 if ($volid !~ m
|^/|) {
591 eval { ($path, $owner) = PVE
::Storage
::path
($storecfg, $volid); };
592 if ($owner && ($owner == $vmid)) {
600 my $test_deallocate_drive = sub {
601 my ($storecfg, $vmid, $key, $drive, $force) = @_;
603 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
604 my $volid = $drive->{file
};
605 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
606 if ($force || $key =~ m/^unused/) {
607 my $sid = PVE
::Storage
::parse_volume_id
($volid);
616 my $delete_drive = sub {
617 my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
619 if (!PVE
::QemuServer
::drive_is_cdrom
($drive)) {
620 my $volid = $drive->{file
};
621 if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
622 if ($force || $key =~ m/^unused/) {
623 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
626 PVE
::QemuServer
::add_unused_volume
($conf, $volid, $vmid);
631 delete $conf->{$key};
634 my $vmconfig_delete_option = sub {
635 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
637 return if !defined($conf->{$opt});
639 my $isDisk = PVE
::QemuServer
::valid_drivename
($opt)|| ($opt =~ m/^unused/);
642 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
644 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
645 if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
646 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
650 die "error hot-unplug $opt" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
653 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
654 &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
656 delete $conf->{$opt};
659 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
662 my $safe_num_ne = sub {
665 return 0 if !defined($a) && !defined($b);
666 return 1 if !defined($a);
667 return 1 if !defined($b);
672 my $vmconfig_update_disk = sub {
673 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = @_;
675 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
677 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { #cdrom
678 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
680 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
685 if (my $old_drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt})) {
687 my $media = $drive->{media
} || 'disk';
688 my $oldmedia = $old_drive->{media
} || 'disk';
689 die "unable to change media type\n" if $media ne $oldmedia;
691 if (!PVE
::QemuServer
::drive_is_cdrom
($old_drive) &&
692 ($drive->{file
} ne $old_drive->{file
})) { # delete old disks
694 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
695 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
698 if(&$safe_num_ne($drive->{mbps
}, $old_drive->{mbps
}) ||
699 &$safe_num_ne($drive->{mbps_rd
}, $old_drive->{mbps_rd
}) ||
700 &$safe_num_ne($drive->{mbps_wr
}, $old_drive->{mbps_wr
}) ||
701 &$safe_num_ne($drive->{iops
}, $old_drive->{iops
}) ||
702 &$safe_num_ne($drive->{iops_rd
}, $old_drive->{iops_rd
}) ||
703 &$safe_num_ne($drive->{iops_wr
}, $old_drive->{iops_wr
})) {
704 PVE
::QemuServer
::qemu_block_set_io_throttle
($vmid,"drive-$opt", $drive->{mbps
}*1024*1024,
705 $drive->{mbps_rd
}*1024*1024, $drive->{mbps_wr
}*1024*1024,
706 $drive->{iops
}, $drive->{iops_rd
}, $drive->{iops_wr
})
707 if !PVE
::QemuServer
::drive_is_cdrom
($drive);
712 &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt => $value});
713 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
715 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
716 $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
718 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # cdrom
720 if (PVE
::QemuServer
::check_running
($vmid)) {
721 if ($drive->{file
} eq 'none') {
722 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt");
724 my $path = PVE
::QemuServer
::get_iso_path
($storecfg, $vmid, $drive->{file
});
725 PVE
::QemuServer
::vm_mon_cmd
($vmid, "eject",force
=> JSON
::true
,device
=> "drive-$opt"); #force eject if locked
726 PVE
::QemuServer
::vm_mon_cmd
($vmid, "change",device
=> "drive-$opt",target
=> "$path") if $path;
730 } else { # hotplug new disks
732 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $drive);
736 my $vmconfig_update_net = sub {
737 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
740 #if online update, then unplug first
741 die "error hot-unplug $opt for update" if !PVE
::QemuServer
::vm_deviceunplug
($vmid, $conf, $opt);
744 $conf->{$opt} = $value;
745 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
746 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
748 my $net = PVE
::QemuServer
::parse_net
($conf->{$opt});
750 die "error hotplug $opt" if !PVE
::QemuServer
::vm_deviceplug
($storecfg, $conf, $vmid, $opt, $net);
753 my $vm_config_perm_list = [
763 __PACKAGE__-
>register_method({
765 path
=> '{vmid}/config',
769 description
=> "Set virtual machine options.",
771 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
774 additionalProperties
=> 0,
775 properties
=> PVE
::QemuServer
::json_config_properties
(
777 node
=> get_standard_option
('pve-node'),
778 vmid
=> get_standard_option
('pve-vmid'),
779 skiplock
=> get_standard_option
('skiplock'),
781 type
=> 'string', format
=> 'pve-configid-list',
782 description
=> "A list of settings you want to delete.",
787 description
=> $opt_force_description,
789 requires
=> 'delete',
793 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
799 returns
=> { type
=> 'null'},
803 my $rpcenv = PVE
::RPCEnvironment
::get
();
805 my $authuser = $rpcenv->get_user();
807 my $node = extract_param
($param, 'node');
809 my $vmid = extract_param
($param, 'vmid');
811 my $digest = extract_param
($param, 'digest');
813 my @paramarr = (); # used for log message
814 foreach my $key (keys %$param) {
815 push @paramarr, "-$key", $param->{$key};
818 my $skiplock = extract_param
($param, 'skiplock');
819 raise_param_exc
({ skiplock
=> "Only root may use this option." })
820 if $skiplock && $authuser ne 'root@pam';
822 my $delete_str = extract_param
($param, 'delete');
824 my $force = extract_param
($param, 'force');
826 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
828 my $storecfg = PVE
::Storage
::config
();
830 my $defaults = PVE
::QemuServer
::load_defaults
();
832 &$resolve_cdrom_alias($param);
834 # now try to verify all parameters
837 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
838 $opt = 'ide2' if $opt eq 'cdrom';
839 raise_param_exc
({ delete => "you can't use '-$opt' and " .
840 "-delete $opt' at the same time" })
841 if defined($param->{$opt});
843 if (!PVE
::QemuServer
::option_exists
($opt)) {
844 raise_param_exc
({ delete => "unknown option '$opt'" });
850 foreach my $opt (keys %$param) {
851 if (PVE
::QemuServer
::valid_drivename
($opt)) {
853 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
854 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
855 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
856 } elsif ($opt =~ m/^net(\d+)$/) {
858 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
859 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
863 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
865 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
867 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
871 my $conf = PVE
::QemuServer
::load_config
($vmid);
873 die "checksum missmatch (file change by other user?)\n"
874 if $digest && $digest ne $conf->{digest
};
876 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
878 if ($param->{memory
} || defined($param->{balloon
})) {
879 my $maxmem = $param->{memory
} || $conf->{memory
} || $defaults->{memory
};
880 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{balloon
};
882 die "balloon value too large (must be smaller than assigned memory)\n"
883 if $balloon > $maxmem;
886 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
888 foreach my $opt (@delete) { # delete
889 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
890 &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
893 my $running = PVE
::QemuServer
::check_running
($vmid);
895 foreach my $opt (keys %$param) { # add/change
897 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
899 next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
901 if (PVE
::QemuServer
::valid_drivename
($opt)) {
903 &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
904 $opt, $param->{$opt}, $force);
906 } elsif ($opt =~ m/^net(\d+)$/) { #nics
908 &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
909 $opt, $param->{$opt});
913 $conf->{$opt} = $param->{$opt};
914 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
918 # allow manual ballooning if shares is set to zero
919 if ($running && defined($param->{balloon
}) &&
920 defined($conf->{shares
}) && ($conf->{shares
} == 0)) {
921 my $balloon = $param->{'balloon'} || $conf->{memory
} || $defaults->{memory
};
922 PVE
::QemuServer
::vm_mon_cmd
($vmid, "balloon", value
=> $balloon*1024*1024);
927 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
933 __PACKAGE__-
>register_method({
934 name
=> 'destroy_vm',
939 description
=> "Destroy the vm (also delete all used/owned volumes).",
941 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
944 additionalProperties
=> 0,
946 node
=> get_standard_option
('pve-node'),
947 vmid
=> get_standard_option
('pve-vmid'),
948 skiplock
=> get_standard_option
('skiplock'),
957 my $rpcenv = PVE
::RPCEnvironment
::get
();
959 my $authuser = $rpcenv->get_user();
961 my $vmid = $param->{vmid
};
963 my $skiplock = $param->{skiplock
};
964 raise_param_exc
({ skiplock
=> "Only root may use this option." })
965 if $skiplock && $authuser ne 'root@pam';
968 my $conf = PVE
::QemuServer
::load_config
($vmid);
970 my $storecfg = PVE
::Storage
::config
();
972 my $delVMfromPoolFn = sub {
973 my $usercfg = cfs_read_file
("user.cfg");
974 if (my $pool = $usercfg->{vms
}->{$vmid}) {
975 if (my $data = $usercfg->{pools
}->{$pool}) {
976 delete $data->{vms
}->{$vmid};
977 delete $usercfg->{vms
}->{$vmid};
978 cfs_write_file
("user.cfg", $usercfg);
986 syslog
('info', "destroy VM $vmid: $upid\n");
988 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
990 PVE
::AccessControl
::lock_user_config
($delVMfromPoolFn, "pool cleanup failed");
993 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
996 __PACKAGE__-
>register_method({
998 path
=> '{vmid}/unlink',
1002 description
=> "Unlink/delete disk images.",
1004 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1007 additionalProperties
=> 0,
1009 node
=> get_standard_option
('pve-node'),
1010 vmid
=> get_standard_option
('pve-vmid'),
1012 type
=> 'string', format
=> 'pve-configid-list',
1013 description
=> "A list of disk IDs you want to delete.",
1017 description
=> $opt_force_description,
1022 returns
=> { type
=> 'null'},
1026 $param->{delete} = extract_param
($param, 'idlist');
1028 __PACKAGE__-
>update_vm($param);
1035 __PACKAGE__-
>register_method({
1037 path
=> '{vmid}/vncproxy',
1041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1043 description
=> "Creates a TCP VNC proxy connections.",
1045 additionalProperties
=> 0,
1047 node
=> get_standard_option
('pve-node'),
1048 vmid
=> get_standard_option
('pve-vmid'),
1052 additionalProperties
=> 0,
1054 user
=> { type
=> 'string' },
1055 ticket
=> { type
=> 'string' },
1056 cert
=> { type
=> 'string' },
1057 port
=> { type
=> 'integer' },
1058 upid
=> { type
=> 'string' },
1064 my $rpcenv = PVE
::RPCEnvironment
::get
();
1066 my $authuser = $rpcenv->get_user();
1068 my $vmid = $param->{vmid
};
1069 my $node = $param->{node
};
1071 my $authpath = "/vms/$vmid";
1073 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1075 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1078 my $port = PVE
::Tools
::next_vnc_port
();
1082 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1083 $remip = PVE
::Cluster
::remote_node_ip
($node);
1086 # NOTE: kvm VNC traffic is already TLS encrypted
1087 my $remcmd = $remip ?
['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
1094 syslog
('info', "starting vnc proxy $upid\n");
1096 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1098 my $qmstr = join(' ', @$qmcmd);
1100 # also redirect stderr (else we get RFB protocol errors)
1101 my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
1103 PVE
::Tools
::run_command
($cmd);
1108 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1110 PVE
::Tools
::wait_for_vnc_port
($port);
1121 __PACKAGE__-
>register_method({
1123 path
=> '{vmid}/status',
1126 description
=> "Directory index",
1131 additionalProperties
=> 0,
1133 node
=> get_standard_option
('pve-node'),
1134 vmid
=> get_standard_option
('pve-vmid'),
1142 subdir
=> { type
=> 'string' },
1145 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1151 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1154 { subdir
=> 'current' },
1155 { subdir
=> 'start' },
1156 { subdir
=> 'stop' },
1162 my $vm_is_ha_managed = sub {
1165 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
1166 if (PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1)) {
1172 __PACKAGE__-
>register_method({
1173 name
=> 'vm_status',
1174 path
=> '{vmid}/status/current',
1177 protected
=> 1, # qemu pid files are only readable by root
1178 description
=> "Get virtual machine status.",
1180 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1183 additionalProperties
=> 0,
1185 node
=> get_standard_option
('pve-node'),
1186 vmid
=> get_standard_option
('pve-vmid'),
1189 returns
=> { type
=> 'object' },
1194 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1196 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1197 my $status = $vmstatus->{$param->{vmid
}};
1199 $status->{ha
} = &$vm_is_ha_managed($param->{vmid
});
1204 __PACKAGE__-
>register_method({
1206 path
=> '{vmid}/status/start',
1210 description
=> "Start virtual machine.",
1212 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1215 additionalProperties
=> 0,
1217 node
=> get_standard_option
('pve-node'),
1218 vmid
=> get_standard_option
('pve-vmid'),
1219 skiplock
=> get_standard_option
('skiplock'),
1220 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1221 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1231 my $rpcenv = PVE
::RPCEnvironment
::get
();
1233 my $authuser = $rpcenv->get_user();
1235 my $node = extract_param
($param, 'node');
1237 my $vmid = extract_param
($param, 'vmid');
1239 my $stateuri = extract_param
($param, 'stateuri');
1240 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1241 if $stateuri && $authuser ne 'root@pam';
1243 my $skiplock = extract_param
($param, 'skiplock');
1244 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1245 if $skiplock && $authuser ne 'root@pam';
1247 my $migratedfrom = extract_param
($param, 'migratedfrom');
1248 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1249 if $migratedfrom && $authuser ne 'root@pam';
1251 my $storecfg = PVE
::Storage
::config
();
1253 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1254 $rpcenv->{type
} ne 'ha') {
1259 my $service = "pvevm:$vmid";
1261 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1263 print "Executing HA start for VM $vmid\n";
1265 PVE
::Tools
::run_command
($cmd);
1270 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1277 syslog
('info', "start VM $vmid: $upid\n");
1279 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
1284 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1288 __PACKAGE__-
>register_method({
1290 path
=> '{vmid}/status/stop',
1294 description
=> "Stop virtual machine.",
1296 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1299 additionalProperties
=> 0,
1301 node
=> get_standard_option
('pve-node'),
1302 vmid
=> get_standard_option
('pve-vmid'),
1303 skiplock
=> get_standard_option
('skiplock'),
1304 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1306 description
=> "Wait maximal timeout seconds.",
1312 description
=> "Do not decativate storage volumes.",
1325 my $rpcenv = PVE
::RPCEnvironment
::get
();
1327 my $authuser = $rpcenv->get_user();
1329 my $node = extract_param
($param, 'node');
1331 my $vmid = extract_param
($param, 'vmid');
1333 my $skiplock = extract_param
($param, 'skiplock');
1334 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1335 if $skiplock && $authuser ne 'root@pam';
1337 my $keepActive = extract_param
($param, 'keepActive');
1338 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1339 if $keepActive && $authuser ne 'root@pam';
1341 my $migratedfrom = extract_param
($param, 'migratedfrom');
1342 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1343 if $migratedfrom && $authuser ne 'root@pam';
1346 my $storecfg = PVE
::Storage
::config
();
1348 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1353 my $service = "pvevm:$vmid";
1355 my $cmd = ['clusvcadm', '-d', $service];
1357 print "Executing HA stop for VM $vmid\n";
1359 PVE
::Tools
::run_command
($cmd);
1364 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1370 syslog
('info', "stop VM $vmid: $upid\n");
1372 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1373 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1378 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1382 __PACKAGE__-
>register_method({
1384 path
=> '{vmid}/status/reset',
1388 description
=> "Reset virtual machine.",
1390 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1393 additionalProperties
=> 0,
1395 node
=> get_standard_option
('pve-node'),
1396 vmid
=> get_standard_option
('pve-vmid'),
1397 skiplock
=> get_standard_option
('skiplock'),
1406 my $rpcenv = PVE
::RPCEnvironment
::get
();
1408 my $authuser = $rpcenv->get_user();
1410 my $node = extract_param
($param, 'node');
1412 my $vmid = extract_param
($param, 'vmid');
1414 my $skiplock = extract_param
($param, 'skiplock');
1415 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1416 if $skiplock && $authuser ne 'root@pam';
1418 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1423 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1428 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1431 __PACKAGE__-
>register_method({
1432 name
=> 'vm_shutdown',
1433 path
=> '{vmid}/status/shutdown',
1437 description
=> "Shutdown virtual machine.",
1439 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1442 additionalProperties
=> 0,
1444 node
=> get_standard_option
('pve-node'),
1445 vmid
=> get_standard_option
('pve-vmid'),
1446 skiplock
=> get_standard_option
('skiplock'),
1448 description
=> "Wait maximal timeout seconds.",
1454 description
=> "Make sure the VM stops.",
1460 description
=> "Do not decativate storage volumes.",
1473 my $rpcenv = PVE
::RPCEnvironment
::get
();
1475 my $authuser = $rpcenv->get_user();
1477 my $node = extract_param
($param, 'node');
1479 my $vmid = extract_param
($param, 'vmid');
1481 my $skiplock = extract_param
($param, 'skiplock');
1482 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1483 if $skiplock && $authuser ne 'root@pam';
1485 my $keepActive = extract_param
($param, 'keepActive');
1486 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1487 if $keepActive && $authuser ne 'root@pam';
1489 my $storecfg = PVE
::Storage
::config
();
1494 syslog
('info', "shutdown VM $vmid: $upid\n");
1496 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1497 1, $param->{forceStop
}, $keepActive);
1502 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1505 __PACKAGE__-
>register_method({
1506 name
=> 'vm_suspend',
1507 path
=> '{vmid}/status/suspend',
1511 description
=> "Suspend virtual machine.",
1513 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1516 additionalProperties
=> 0,
1518 node
=> get_standard_option
('pve-node'),
1519 vmid
=> get_standard_option
('pve-vmid'),
1520 skiplock
=> get_standard_option
('skiplock'),
1529 my $rpcenv = PVE
::RPCEnvironment
::get
();
1531 my $authuser = $rpcenv->get_user();
1533 my $node = extract_param
($param, 'node');
1535 my $vmid = extract_param
($param, 'vmid');
1537 my $skiplock = extract_param
($param, 'skiplock');
1538 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1539 if $skiplock && $authuser ne 'root@pam';
1541 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1546 syslog
('info', "suspend VM $vmid: $upid\n");
1548 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1553 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1556 __PACKAGE__-
>register_method({
1557 name
=> 'vm_resume',
1558 path
=> '{vmid}/status/resume',
1562 description
=> "Resume virtual machine.",
1564 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1567 additionalProperties
=> 0,
1569 node
=> get_standard_option
('pve-node'),
1570 vmid
=> get_standard_option
('pve-vmid'),
1571 skiplock
=> get_standard_option
('skiplock'),
1580 my $rpcenv = PVE
::RPCEnvironment
::get
();
1582 my $authuser = $rpcenv->get_user();
1584 my $node = extract_param
($param, 'node');
1586 my $vmid = extract_param
($param, 'vmid');
1588 my $skiplock = extract_param
($param, 'skiplock');
1589 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1590 if $skiplock && $authuser ne 'root@pam';
1592 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1597 syslog
('info', "resume VM $vmid: $upid\n");
1599 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1604 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1607 __PACKAGE__-
>register_method({
1608 name
=> 'vm_sendkey',
1609 path
=> '{vmid}/sendkey',
1613 description
=> "Send key event to virtual machine.",
1615 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1618 additionalProperties
=> 0,
1620 node
=> get_standard_option
('pve-node'),
1621 vmid
=> get_standard_option
('pve-vmid'),
1622 skiplock
=> get_standard_option
('skiplock'),
1624 description
=> "The key (qemu monitor encoding).",
1629 returns
=> { type
=> 'null'},
1633 my $rpcenv = PVE
::RPCEnvironment
::get
();
1635 my $authuser = $rpcenv->get_user();
1637 my $node = extract_param
($param, 'node');
1639 my $vmid = extract_param
($param, 'vmid');
1641 my $skiplock = extract_param
($param, 'skiplock');
1642 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1643 if $skiplock && $authuser ne 'root@pam';
1645 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1650 __PACKAGE__-
>register_method({
1651 name
=> 'vm_feature',
1652 path
=> '{vmid}/feature',
1656 description
=> "Check if feature for virtual machine is available.",
1658 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1661 additionalProperties
=> 0,
1663 node
=> get_standard_option
('pve-node'),
1664 vmid
=> get_standard_option
('pve-vmid'),
1666 description
=> "Feature to check.",
1668 enum
=> [ 'snapshot', 'clone' ],
1670 snapname
=> get_standard_option
('pve-snapshot-name', {
1682 my $node = extract_param
($param, 'node');
1684 my $vmid = extract_param
($param, 'vmid');
1686 my $snapname = extract_param
($param, 'snapname');
1688 my $feature = extract_param
($param, 'feature');
1690 my $running = PVE
::QemuServer
::check_running
($vmid);
1692 my $conf = PVE
::QemuServer
::load_config
($vmid);
1695 my $snap = $conf->{snapshots
}->{$snapname};
1696 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1699 my $storecfg = PVE
::Storage
::config
();
1701 my $hasfeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
1702 my $res = $hasfeature ?
1 : 0 ;
1706 __PACKAGE__-
>register_method({
1707 name
=> 'migrate_vm',
1708 path
=> '{vmid}/migrate',
1712 description
=> "Migrate virtual machine. Creates a new migration task.",
1714 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1717 additionalProperties
=> 0,
1719 node
=> get_standard_option
('pve-node'),
1720 vmid
=> get_standard_option
('pve-vmid'),
1721 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1724 description
=> "Use online/live migration.",
1729 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
1736 description
=> "the task ID.",
1741 my $rpcenv = PVE
::RPCEnvironment
::get
();
1743 my $authuser = $rpcenv->get_user();
1745 my $target = extract_param
($param, 'target');
1747 my $localnode = PVE
::INotify
::nodename
();
1748 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1750 PVE
::Cluster
::check_cfs_quorum
();
1752 PVE
::Cluster
::check_node_exists
($target);
1754 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1756 my $vmid = extract_param
($param, 'vmid');
1758 raise_param_exc
({ force
=> "Only root may use this option." })
1759 if $param->{force
} && $authuser ne 'root@pam';
1762 my $conf = PVE
::QemuServer
::load_config
($vmid);
1764 # try to detect errors early
1766 PVE
::QemuServer
::check_lock
($conf);
1768 if (PVE
::QemuServer
::check_running
($vmid)) {
1769 die "cant migrate running VM without --online\n"
1770 if !$param->{online
};
1773 my $storecfg = PVE
::Storage
::config
();
1774 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
1776 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type
} ne 'ha') {
1781 my $service = "pvevm:$vmid";
1783 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
1785 print "Executing HA migrate for VM $vmid to node $target\n";
1787 PVE
::Tools
::run_command
($cmd);
1792 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1799 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
1802 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
1807 __PACKAGE__-
>register_method({
1809 path
=> '{vmid}/monitor',
1813 description
=> "Execute Qemu monitor commands.",
1815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
1818 additionalProperties
=> 0,
1820 node
=> get_standard_option
('pve-node'),
1821 vmid
=> get_standard_option
('pve-vmid'),
1824 description
=> "The monitor command.",
1828 returns
=> { type
=> 'string'},
1832 my $vmid = $param->{vmid
};
1834 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
1838 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
1840 $res = "ERROR: $@" if $@;
1845 __PACKAGE__-
>register_method({
1846 name
=> 'resize_vm',
1847 path
=> '{vmid}/resize',
1851 description
=> "Extend volume size.",
1853 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1856 additionalProperties
=> 0,
1858 node
=> get_standard_option
('pve-node'),
1859 vmid
=> get_standard_option
('pve-vmid'),
1860 skiplock
=> get_standard_option
('skiplock'),
1863 description
=> "The disk you want to resize.",
1864 enum
=> [PVE
::QemuServer
::disknames
()],
1868 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1869 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.",
1873 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1879 returns
=> { type
=> 'null'},
1883 my $rpcenv = PVE
::RPCEnvironment
::get
();
1885 my $authuser = $rpcenv->get_user();
1887 my $node = extract_param
($param, 'node');
1889 my $vmid = extract_param
($param, 'vmid');
1891 my $digest = extract_param
($param, 'digest');
1893 my $disk = extract_param
($param, 'disk');
1895 my $sizestr = extract_param
($param, 'size');
1897 my $skiplock = extract_param
($param, 'skiplock');
1898 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1899 if $skiplock && $authuser ne 'root@pam';
1901 my $storecfg = PVE
::Storage
::config
();
1903 my $updatefn = sub {
1905 my $conf = PVE
::QemuServer
::load_config
($vmid);
1907 die "checksum missmatch (file change by other user?)\n"
1908 if $digest && $digest ne $conf->{digest
};
1909 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
1911 die "disk '$disk' does not exist\n" if !$conf->{$disk};
1913 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
1915 my $volid = $drive->{file
};
1917 die "disk '$disk' has no associated volume\n" if !$volid;
1919 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
1921 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1923 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1925 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
1927 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
1928 my ($ext, $newsize, $unit) = ($1, $2, $4);
1931 $newsize = $newsize * 1024;
1932 } elsif ($unit eq 'M') {
1933 $newsize = $newsize * 1024 * 1024;
1934 } elsif ($unit eq 'G') {
1935 $newsize = $newsize * 1024 * 1024 * 1024;
1936 } elsif ($unit eq 'T') {
1937 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
1940 $newsize += $size if $ext;
1941 $newsize = int($newsize);
1943 die "unable to skrink disk size\n" if $newsize < $size;
1945 return if $size == $newsize;
1947 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
1949 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
1951 $drive->{size
} = $newsize;
1952 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1954 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
1957 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1961 __PACKAGE__-
>register_method({
1962 name
=> 'snapshot_list',
1963 path
=> '{vmid}/snapshot',
1965 description
=> "List all snapshots.",
1967 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1970 protected
=> 1, # qemu pid files are only readable by root
1972 additionalProperties
=> 0,
1974 vmid
=> get_standard_option
('pve-vmid'),
1975 node
=> get_standard_option
('pve-node'),
1984 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1989 my $vmid = $param->{vmid
};
1991 my $conf = PVE
::QemuServer
::load_config
($vmid);
1992 my $snaphash = $conf->{snapshots
} || {};
1996 foreach my $name (keys %$snaphash) {
1997 my $d = $snaphash->{$name};
2000 snaptime
=> $d->{snaptime
} || 0,
2001 vmstate
=> $d->{vmstate
} ?
1 : 0,
2002 description
=> $d->{description
} || '',
2004 $item->{parent
} = $d->{parent
} if $d->{parent
};
2005 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2009 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2010 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2011 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2013 push @$res, $current;
2018 __PACKAGE__-
>register_method({
2020 path
=> '{vmid}/snapshot',
2024 description
=> "Snapshot a VM.",
2026 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2029 additionalProperties
=> 0,
2031 node
=> get_standard_option
('pve-node'),
2032 vmid
=> get_standard_option
('pve-vmid'),
2033 snapname
=> get_standard_option
('pve-snapshot-name'),
2037 description
=> "Save the vmstate",
2042 description
=> "Freeze the filesystem",
2047 description
=> "A textual description or comment.",
2053 description
=> "the task ID.",
2058 my $rpcenv = PVE
::RPCEnvironment
::get
();
2060 my $authuser = $rpcenv->get_user();
2062 my $node = extract_param
($param, 'node');
2064 my $vmid = extract_param
($param, 'vmid');
2066 my $snapname = extract_param
($param, 'snapname');
2068 die "unable to use snapshot name 'current' (reserved name)\n"
2069 if $snapname eq 'current';
2072 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2073 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2074 $param->{freezefs
}, $param->{description
});
2077 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2080 __PACKAGE__-
>register_method({
2081 name
=> 'snapshot_cmd_idx',
2082 path
=> '{vmid}/snapshot/{snapname}',
2089 additionalProperties
=> 0,
2091 vmid
=> get_standard_option
('pve-vmid'),
2092 node
=> get_standard_option
('pve-node'),
2093 snapname
=> get_standard_option
('pve-snapshot-name'),
2102 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2109 push @$res, { cmd
=> 'rollback' };
2110 push @$res, { cmd
=> 'config' };
2115 __PACKAGE__-
>register_method({
2116 name
=> 'update_snapshot_config',
2117 path
=> '{vmid}/snapshot/{snapname}/config',
2121 description
=> "Update snapshot metadata.",
2123 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2126 additionalProperties
=> 0,
2128 node
=> get_standard_option
('pve-node'),
2129 vmid
=> get_standard_option
('pve-vmid'),
2130 snapname
=> get_standard_option
('pve-snapshot-name'),
2134 description
=> "A textual description or comment.",
2138 returns
=> { type
=> 'null' },
2142 my $rpcenv = PVE
::RPCEnvironment
::get
();
2144 my $authuser = $rpcenv->get_user();
2146 my $vmid = extract_param
($param, 'vmid');
2148 my $snapname = extract_param
($param, 'snapname');
2150 return undef if !defined($param->{description
});
2152 my $updatefn = sub {
2154 my $conf = PVE
::QemuServer
::load_config
($vmid);
2156 PVE
::QemuServer
::check_lock
($conf);
2158 my $snap = $conf->{snapshots
}->{$snapname};
2160 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2162 $snap->{description
} = $param->{description
} if defined($param->{description
});
2164 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2167 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2172 __PACKAGE__-
>register_method({
2173 name
=> 'get_snapshot_config',
2174 path
=> '{vmid}/snapshot/{snapname}/config',
2177 description
=> "Get snapshot configuration",
2179 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2182 additionalProperties
=> 0,
2184 node
=> get_standard_option
('pve-node'),
2185 vmid
=> get_standard_option
('pve-vmid'),
2186 snapname
=> get_standard_option
('pve-snapshot-name'),
2189 returns
=> { type
=> "object" },
2193 my $rpcenv = PVE
::RPCEnvironment
::get
();
2195 my $authuser = $rpcenv->get_user();
2197 my $vmid = extract_param
($param, 'vmid');
2199 my $snapname = extract_param
($param, 'snapname');
2201 my $conf = PVE
::QemuServer
::load_config
($vmid);
2203 my $snap = $conf->{snapshots
}->{$snapname};
2205 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2210 __PACKAGE__-
>register_method({
2212 path
=> '{vmid}/snapshot/{snapname}/rollback',
2216 description
=> "Rollback VM state to specified snapshot.",
2218 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2221 additionalProperties
=> 0,
2223 node
=> get_standard_option
('pve-node'),
2224 vmid
=> get_standard_option
('pve-vmid'),
2225 snapname
=> get_standard_option
('pve-snapshot-name'),
2230 description
=> "the task ID.",
2235 my $rpcenv = PVE
::RPCEnvironment
::get
();
2237 my $authuser = $rpcenv->get_user();
2239 my $node = extract_param
($param, 'node');
2241 my $vmid = extract_param
($param, 'vmid');
2243 my $snapname = extract_param
($param, 'snapname');
2246 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2247 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
2250 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2253 __PACKAGE__-
>register_method({
2254 name
=> 'delsnapshot',
2255 path
=> '{vmid}/snapshot/{snapname}',
2259 description
=> "Delete a VM snapshot.",
2261 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2264 additionalProperties
=> 0,
2266 node
=> get_standard_option
('pve-node'),
2267 vmid
=> get_standard_option
('pve-vmid'),
2268 snapname
=> get_standard_option
('pve-snapshot-name'),
2272 description
=> "For removal from config file, even if removing disk snapshots fails.",
2278 description
=> "the task ID.",
2283 my $rpcenv = PVE
::RPCEnvironment
::get
();
2285 my $authuser = $rpcenv->get_user();
2287 my $node = extract_param
($param, 'node');
2289 my $vmid = extract_param
($param, 'vmid');
2291 my $snapname = extract_param
($param, 'snapname');
2294 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
2295 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
2298 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
2301 __PACKAGE__-
>register_method({
2303 path
=> '{vmid}/template',
2307 description
=> "Create a Template.",
2309 additionalProperties
=> 0,
2311 node
=> get_standard_option
('pve-node'),
2312 vmid
=> get_standard_option
('pve-vmid'),
2316 description
=> "If you want to convert only 1 disk to base image.",
2317 enum
=> [PVE
::QemuServer
::disknames
()],
2322 returns
=> { type
=> 'null'},
2326 my $rpcenv = PVE
::RPCEnvironment
::get
();
2328 my $authuser = $rpcenv->get_user();
2330 my $node = extract_param
($param, 'node');
2332 my $vmid = extract_param
($param, 'vmid');
2334 my $disk = extract_param
($param, 'disk');
2336 my $updatefn = sub {
2338 my $conf = PVE
::QemuServer
::load_config
($vmid);
2340 PVE
::QemuServer
::check_lock
($conf);
2342 die "you can't convert a template to a template"
2343 if PVE
::QemuServer
::is_template
($conf) && !$disk;
2345 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
2347 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
2349 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2352 PVE
::QemuServer
::lock_config
($vmid, $updatefn);