1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::RPCEnvironment
;
19 use PVE
::AccessControl
;
23 use PVE
::API2
::Firewall
::VM
;
26 use Data
::Dumper
; # fixme: remove
28 use base
qw(PVE::RESTHandler);
30 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.";
32 my $resolve_cdrom_alias = sub {
35 if (my $value = $param->{cdrom
}) {
36 $value .= ",media=cdrom" if $value !~ m/media=/;
37 $param->{ide2
} = $value;
38 delete $param->{cdrom
};
42 my $check_storage_access = sub {
43 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
45 PVE
::QemuServer
::foreach_drive
($settings, sub {
46 my ($ds, $drive) = @_;
48 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
50 my $volid = $drive->{file
};
52 if (!$volid || $volid eq 'none') {
54 } elsif ($isCDROM && ($volid eq 'cdrom')) {
55 $rpcenv->check($authuser, "/", ['Sys.Console']);
56 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
57 my ($storeid, $size) = ($2 || $default_storage, $3);
58 die "no storage ID specified (and no default storage)\n" if !$storeid;
59 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
61 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
66 my $check_storage_access_clone = sub {
67 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
71 PVE
::QemuServer
::foreach_drive
($conf, sub {
72 my ($ds, $drive) = @_;
74 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
76 my $volid = $drive->{file
};
78 return if !$volid || $volid eq 'none';
81 if ($volid eq 'cdrom') {
82 $rpcenv->check($authuser, "/", ['Sys.Console']);
84 # we simply allow access
85 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
86 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
87 $sharedvm = 0 if !$scfg->{shared
};
91 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
92 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
93 $sharedvm = 0 if !$scfg->{shared
};
95 $sid = $storage if $storage;
96 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
103 # Note: $pool is only needed when creating a VM, because pool permissions
104 # are automatically inherited if VM already exists inside a pool.
105 my $create_disks = sub {
106 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
111 PVE
::QemuServer
::foreach_drive
($settings, sub {
112 my ($ds, $disk) = @_;
114 my $volid = $disk->{file
};
116 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
117 delete $disk->{size
};
118 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
119 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
120 my ($storeid, $size) = ($2 || $default_storage, $3);
121 die "no storage ID specified (and no default storage)\n" if !$storeid;
122 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
123 my $fmt = $disk->{format
} || $defformat;
124 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
125 $fmt, undef, $size*1024*1024);
126 $disk->{file
} = $volid;
127 $disk->{size
} = $size*1024*1024*1024;
128 push @$vollist, $volid;
129 delete $disk->{format
}; # no longer needed
130 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
133 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
135 my $volid_is_new = 1;
138 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
139 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
144 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
146 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
148 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
150 die "volume $volid does not exists\n" if !$size;
152 $disk->{size
} = $size;
155 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
159 # free allocated images on error
161 syslog
('err', "VM $vmid creating disks failed");
162 foreach my $volid (@$vollist) {
163 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
169 # modify vm config if everything went well
170 foreach my $ds (keys %$res) {
171 $conf->{$ds} = $res->{$ds};
177 my $check_vm_modify_config_perm = sub {
178 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
180 return 1 if $authuser eq 'root@pam';
182 foreach my $opt (@$key_list) {
183 # disk checks need to be done somewhere else
184 next if PVE
::QemuServer
::valid_drivename
($opt);
186 if ($opt eq 'sockets' || $opt eq 'cores' ||
187 $opt eq 'cpu' || $opt eq 'smp' || $opt eq 'vcpus' ||
188 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
190 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
191 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
192 } elsif ($opt eq 'args' || $opt eq 'lock') {
193 die "only root can set '$opt' config\n";
194 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
195 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
196 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
197 } elsif ($opt =~ m/^net\d+$/) {
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
200 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
207 my $check_protection = sub {
208 my ($vm_conf, $err_msg) = @_;
210 if ($vm_conf->{protection
}) {
211 die "$err_msg - protection mode enabled\n";
215 __PACKAGE__-
>register_method({
219 description
=> "Virtual machine index (per node).",
221 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
225 protected
=> 1, # qemu pid files are only readable by root
227 additionalProperties
=> 0,
229 node
=> get_standard_option
('pve-node'),
238 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
243 my $rpcenv = PVE
::RPCEnvironment
::get
();
244 my $authuser = $rpcenv->get_user();
246 my $vmstatus = PVE
::QemuServer
::vmstatus
();
249 foreach my $vmid (keys %$vmstatus) {
250 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
252 my $data = $vmstatus->{$vmid};
253 $data->{vmid
} = int($vmid);
262 __PACKAGE__-
>register_method({
266 description
=> "Create or restore a virtual machine.",
268 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
269 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
270 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
271 user
=> 'all', # check inside
276 additionalProperties
=> 0,
277 properties
=> PVE
::QemuServer
::json_config_properties
(
279 node
=> get_standard_option
('pve-node'),
280 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
282 description
=> "The backup file.",
286 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
288 storage
=> get_standard_option
('pve-storage-id', {
289 description
=> "Default storage.",
291 completion
=> \
&PVE
::QemuServer
::complete_storage
,
296 description
=> "Allow to overwrite existing VM.",
297 requires
=> 'archive',
302 description
=> "Assign a unique random ethernet address.",
303 requires
=> 'archive',
307 type
=> 'string', format
=> 'pve-poolid',
308 description
=> "Add the VM to the specified pool.",
318 my $rpcenv = PVE
::RPCEnvironment
::get
();
320 my $authuser = $rpcenv->get_user();
322 my $node = extract_param
($param, 'node');
324 my $vmid = extract_param
($param, 'vmid');
326 my $archive = extract_param
($param, 'archive');
328 my $storage = extract_param
($param, 'storage');
330 my $force = extract_param
($param, 'force');
332 my $unique = extract_param
($param, 'unique');
334 my $pool = extract_param
($param, 'pool');
336 my $filename = PVE
::QemuServer
::config_file
($vmid);
338 my $storecfg = PVE
::Storage
::config
();
340 PVE
::Cluster
::check_cfs_quorum
();
342 if (defined($pool)) {
343 $rpcenv->check_pool_exist($pool);
346 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
347 if defined($storage);
349 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
351 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
353 } elsif ($archive && $force && (-f
$filename) &&
354 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
355 # OK: user has VM.Backup permissions, and want to restore an existing VM
361 &$resolve_cdrom_alias($param);
363 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
365 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
367 foreach my $opt (keys %$param) {
368 if (PVE
::QemuServer
::valid_drivename
($opt)) {
369 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
370 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
372 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
373 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
377 PVE
::QemuServer
::add_random_macs
($param);
379 my $keystr = join(' ', keys %$param);
380 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
382 if ($archive eq '-') {
383 die "pipe requires cli environment\n"
384 if $rpcenv->{type
} ne 'cli';
386 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
387 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
391 my $restorefn = sub {
392 my $vmlist = PVE
::Cluster
::get_vmlist
();
393 if ($vmlist->{ids
}->{$vmid}) {
394 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
395 if ($current_node eq $node) {
396 my $conf = PVE
::QemuServer
::load_config
($vmid);
398 &$check_protection($conf, "unable to restore VM $vmid");
400 die "unable to restore vm $vmid - config file already exists\n"
403 die "unable to restore vm $vmid - vm is running\n"
404 if PVE
::QemuServer
::check_running
($vmid);
406 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
411 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
414 unique
=> $unique });
416 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
419 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
425 PVE
::Cluster
::check_vmid_unused
($vmid);
435 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
437 # try to be smart about bootdisk
438 my @disks = PVE
::QemuServer
::disknames
();
440 foreach my $ds (reverse @disks) {
441 next if !$conf->{$ds};
442 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
443 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
447 if (!$conf->{bootdisk
} && $firstdisk) {
448 $conf->{bootdisk
} = $firstdisk;
451 # auto generate uuid if user did not specify smbios1 option
452 if (!$conf->{smbios1
}) {
453 my ($uuid, $uuid_str);
454 UUID
::generate
($uuid);
455 UUID
::unparse
($uuid, $uuid_str);
456 $conf->{smbios1
} = "uuid=$uuid_str";
459 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
465 foreach my $volid (@$vollist) {
466 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
469 die "create failed - $err";
472 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
475 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
478 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
481 __PACKAGE__-
>register_method({
486 description
=> "Directory index",
491 additionalProperties
=> 0,
493 node
=> get_standard_option
('pve-node'),
494 vmid
=> get_standard_option
('pve-vmid'),
502 subdir
=> { type
=> 'string' },
505 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
511 { subdir
=> 'config' },
512 { subdir
=> 'pending' },
513 { subdir
=> 'status' },
514 { subdir
=> 'unlink' },
515 { subdir
=> 'vncproxy' },
516 { subdir
=> 'migrate' },
517 { subdir
=> 'resize' },
518 { subdir
=> 'move' },
520 { subdir
=> 'rrddata' },
521 { subdir
=> 'monitor' },
522 { subdir
=> 'snapshot' },
523 { subdir
=> 'spiceproxy' },
524 { subdir
=> 'sendkey' },
525 { subdir
=> 'firewall' },
531 __PACKAGE__-
>register_method ({
532 subclass
=> "PVE::API2::Firewall::VM",
533 path
=> '{vmid}/firewall',
536 __PACKAGE__-
>register_method({
538 path
=> '{vmid}/rrd',
540 protected
=> 1, # fixme: can we avoid that?
542 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
544 description
=> "Read VM RRD statistics (returns PNG)",
546 additionalProperties
=> 0,
548 node
=> get_standard_option
('pve-node'),
549 vmid
=> get_standard_option
('pve-vmid'),
551 description
=> "Specify the time frame you are interested in.",
553 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
556 description
=> "The list of datasources you want to display.",
557 type
=> 'string', format
=> 'pve-configid-list',
560 description
=> "The RRD consolidation function",
562 enum
=> [ 'AVERAGE', 'MAX' ],
570 filename
=> { type
=> 'string' },
576 return PVE
::Cluster
::create_rrd_graph
(
577 "pve2-vm/$param->{vmid}", $param->{timeframe
},
578 $param->{ds
}, $param->{cf
});
582 __PACKAGE__-
>register_method({
584 path
=> '{vmid}/rrddata',
586 protected
=> 1, # fixme: can we avoid that?
588 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
590 description
=> "Read VM RRD statistics",
592 additionalProperties
=> 0,
594 node
=> get_standard_option
('pve-node'),
595 vmid
=> get_standard_option
('pve-vmid'),
597 description
=> "Specify the time frame you are interested in.",
599 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
602 description
=> "The RRD consolidation function",
604 enum
=> [ 'AVERAGE', 'MAX' ],
619 return PVE
::Cluster
::create_rrd_data
(
620 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
624 __PACKAGE__-
>register_method({
626 path
=> '{vmid}/config',
629 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
631 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
634 additionalProperties
=> 0,
636 node
=> get_standard_option
('pve-node'),
637 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
639 description
=> "Get current values (instead of pending values).",
651 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
658 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
660 delete $conf->{snapshots
};
662 if (!$param->{current
}) {
663 foreach my $opt (keys %{$conf->{pending
}}) {
664 next if $opt eq 'delete';
665 my $value = $conf->{pending
}->{$opt};
666 next if ref($value); # just to be sure
667 $conf->{$opt} = $value;
669 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
670 foreach my $opt (keys %$pending_delete_hash) {
671 delete $conf->{$opt} if $conf->{$opt};
675 delete $conf->{pending
};
680 __PACKAGE__-
>register_method({
681 name
=> 'vm_pending',
682 path
=> '{vmid}/pending',
685 description
=> "Get virtual machine configuration, including pending changes.",
687 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
690 additionalProperties
=> 0,
692 node
=> get_standard_option
('pve-node'),
693 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
702 description
=> "Configuration option name.",
706 description
=> "Current value.",
711 description
=> "Pending value.",
716 description
=> "Indicates a pending delete request if present and not 0. " .
717 "The value 2 indicates a force-delete request.",
729 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
731 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
735 foreach my $opt (keys %$conf) {
736 next if ref($conf->{$opt});
737 my $item = { key
=> $opt };
738 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
739 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
740 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
744 foreach my $opt (keys %{$conf->{pending
}}) {
745 next if $opt eq 'delete';
746 next if ref($conf->{pending
}->{$opt}); # just to be sure
747 next if defined($conf->{$opt});
748 my $item = { key
=> $opt };
749 $item->{pending
} = $conf->{pending
}->{$opt};
753 while (my ($opt, $force) = each %$pending_delete_hash) {
754 next if $conf->{pending
}->{$opt}; # just to be sure
755 next if $conf->{$opt};
756 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
763 # POST/PUT {vmid}/config implementation
765 # The original API used PUT (idempotent) an we assumed that all operations
766 # are fast. But it turned out that almost any configuration change can
767 # involve hot-plug actions, or disk alloc/free. Such actions can take long
768 # time to complete and have side effects (not idempotent).
770 # The new implementation uses POST and forks a worker process. We added
771 # a new option 'background_delay'. If specified we wait up to
772 # 'background_delay' second for the worker task to complete. It returns null
773 # if the task is finished within that time, else we return the UPID.
775 my $update_vm_api = sub {
776 my ($param, $sync) = @_;
778 my $rpcenv = PVE
::RPCEnvironment
::get
();
780 my $authuser = $rpcenv->get_user();
782 my $node = extract_param
($param, 'node');
784 my $vmid = extract_param
($param, 'vmid');
786 my $digest = extract_param
($param, 'digest');
788 my $background_delay = extract_param
($param, 'background_delay');
790 my @paramarr = (); # used for log message
791 foreach my $key (keys %$param) {
792 push @paramarr, "-$key", $param->{$key};
795 my $skiplock = extract_param
($param, 'skiplock');
796 raise_param_exc
({ skiplock
=> "Only root may use this option." })
797 if $skiplock && $authuser ne 'root@pam';
799 my $delete_str = extract_param
($param, 'delete');
801 my $revert_str = extract_param
($param, 'revert');
803 my $force = extract_param
($param, 'force');
805 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
807 my $storecfg = PVE
::Storage
::config
();
809 my $defaults = PVE
::QemuServer
::load_defaults
();
811 &$resolve_cdrom_alias($param);
813 # now try to verify all parameters
816 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
817 if (!PVE
::QemuServer
::option_exists
($opt)) {
818 raise_param_exc
({ revert
=> "unknown option '$opt'" });
821 raise_param_exc
({ delete => "you can't use '-$opt' and " .
822 "-revert $opt' at the same time" })
823 if defined($param->{$opt});
829 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
830 $opt = 'ide2' if $opt eq 'cdrom';
832 raise_param_exc
({ delete => "you can't use '-$opt' and " .
833 "-delete $opt' at the same time" })
834 if defined($param->{$opt});
836 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
837 "-revert $opt' at the same time" })
840 if (!PVE
::QemuServer
::option_exists
($opt)) {
841 raise_param_exc
({ delete => "unknown option '$opt'" });
847 foreach my $opt (keys %$param) {
848 if (PVE
::QemuServer
::valid_drivename
($opt)) {
850 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
851 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
852 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
853 } elsif ($opt =~ m/^net(\d+)$/) {
855 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
856 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
860 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
862 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
864 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
868 my $conf = PVE
::QemuServer
::load_config
($vmid);
870 die "checksum missmatch (file change by other user?)\n"
871 if $digest && $digest ne $conf->{digest
};
873 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
875 foreach my $opt (keys %$revert) {
876 if (defined($conf->{$opt})) {
877 $param->{$opt} = $conf->{$opt};
878 } elsif (defined($conf->{pending
}->{$opt})) {
883 if ($param->{memory
} || defined($param->{balloon
})) {
884 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
885 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
887 die "balloon value too large (must be smaller than assigned memory)\n"
888 if $balloon && $balloon > $maxmem;
891 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
895 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
897 # write updates to pending section
899 my $modified = {}; # record what $option we modify
901 foreach my $opt (@delete) {
902 $modified->{$opt} = 1;
903 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
904 if ($opt =~ m/^unused/) {
905 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
906 &$check_protection($conf, "can't remove unused disk '$drive->{file}'");
907 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
908 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
909 delete $conf->{$opt};
910 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
912 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
913 &$check_protection($conf, "can't remove drive '$opt'");
914 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
915 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
916 if defined($conf->{pending
}->{$opt});
917 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
918 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
920 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
921 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
925 foreach my $opt (keys %$param) { # add/change
926 $modified->{$opt} = 1;
927 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
928 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
930 if (PVE
::QemuServer
::valid_drivename
($opt)) {
931 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
932 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
933 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
935 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
937 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
938 if defined($conf->{pending
}->{$opt});
940 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
942 $conf->{pending
}->{$opt} = $param->{$opt};
944 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
945 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
948 # remove pending changes when nothing changed
949 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
950 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
951 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
953 return if !scalar(keys %{$conf->{pending
}});
955 my $running = PVE
::QemuServer
::check_running
($vmid);
957 # apply pending changes
959 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
963 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
964 raise_param_exc
($errors) if scalar(keys %$errors);
966 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
976 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
978 if ($background_delay) {
980 # Note: It would be better to do that in the Event based HTTPServer
981 # to avoid blocking call to sleep.
983 my $end_time = time() + $background_delay;
985 my $task = PVE
::Tools
::upid_decode
($upid);
988 while (time() < $end_time) {
989 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
991 sleep(1); # this gets interrupted when child process ends
995 my $status = PVE
::Tools
::upid_read_status
($upid);
996 return undef if $status eq 'OK';
1005 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1008 my $vm_config_perm_list = [
1013 'VM.Config.Network',
1015 'VM.Config.Options',
1018 __PACKAGE__-
>register_method({
1019 name
=> 'update_vm_async',
1020 path
=> '{vmid}/config',
1024 description
=> "Set virtual machine options (asynchrounous API).",
1026 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1029 additionalProperties
=> 0,
1030 properties
=> PVE
::QemuServer
::json_config_properties
(
1032 node
=> get_standard_option
('pve-node'),
1033 vmid
=> get_standard_option
('pve-vmid'),
1034 skiplock
=> get_standard_option
('skiplock'),
1036 type
=> 'string', format
=> 'pve-configid-list',
1037 description
=> "A list of settings you want to delete.",
1041 type
=> 'string', format
=> 'pve-configid-list',
1042 description
=> "Revert a pending change.",
1047 description
=> $opt_force_description,
1049 requires
=> 'delete',
1053 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1057 background_delay
=> {
1059 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1070 code
=> $update_vm_api,
1073 __PACKAGE__-
>register_method({
1074 name
=> 'update_vm',
1075 path
=> '{vmid}/config',
1079 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1081 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1084 additionalProperties
=> 0,
1085 properties
=> PVE
::QemuServer
::json_config_properties
(
1087 node
=> get_standard_option
('pve-node'),
1088 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1089 skiplock
=> get_standard_option
('skiplock'),
1091 type
=> 'string', format
=> 'pve-configid-list',
1092 description
=> "A list of settings you want to delete.",
1096 type
=> 'string', format
=> 'pve-configid-list',
1097 description
=> "Revert a pending change.",
1102 description
=> $opt_force_description,
1104 requires
=> 'delete',
1108 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1114 returns
=> { type
=> 'null' },
1117 &$update_vm_api($param, 1);
1123 __PACKAGE__-
>register_method({
1124 name
=> 'destroy_vm',
1129 description
=> "Destroy the vm (also delete all used/owned volumes).",
1131 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1134 additionalProperties
=> 0,
1136 node
=> get_standard_option
('pve-node'),
1137 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1138 skiplock
=> get_standard_option
('skiplock'),
1147 my $rpcenv = PVE
::RPCEnvironment
::get
();
1149 my $authuser = $rpcenv->get_user();
1151 my $vmid = $param->{vmid
};
1153 my $skiplock = $param->{skiplock
};
1154 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1155 if $skiplock && $authuser ne 'root@pam';
1158 my $conf = PVE
::QemuServer
::load_config
($vmid);
1160 my $storecfg = PVE
::Storage
::config
();
1162 &$check_protection($conf, "can't remove VM $vmid");
1164 die "unable to remove VM $vmid - used in HA resources\n"
1165 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1170 syslog
('info', "destroy VM $vmid: $upid\n");
1172 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1174 PVE
::AccessControl
::remove_vm_access
($vmid);
1176 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1179 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1182 __PACKAGE__-
>register_method({
1184 path
=> '{vmid}/unlink',
1188 description
=> "Unlink/delete disk images.",
1190 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1193 additionalProperties
=> 0,
1195 node
=> get_standard_option
('pve-node'),
1196 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1198 type
=> 'string', format
=> 'pve-configid-list',
1199 description
=> "A list of disk IDs you want to delete.",
1203 description
=> $opt_force_description,
1208 returns
=> { type
=> 'null'},
1212 $param->{delete} = extract_param
($param, 'idlist');
1214 __PACKAGE__-
>update_vm($param);
1221 __PACKAGE__-
>register_method({
1223 path
=> '{vmid}/vncproxy',
1227 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1229 description
=> "Creates a TCP VNC proxy connections.",
1231 additionalProperties
=> 0,
1233 node
=> get_standard_option
('pve-node'),
1234 vmid
=> get_standard_option
('pve-vmid'),
1238 description
=> "starts websockify instead of vncproxy",
1243 additionalProperties
=> 0,
1245 user
=> { type
=> 'string' },
1246 ticket
=> { type
=> 'string' },
1247 cert
=> { type
=> 'string' },
1248 port
=> { type
=> 'integer' },
1249 upid
=> { type
=> 'string' },
1255 my $rpcenv = PVE
::RPCEnvironment
::get
();
1257 my $authuser = $rpcenv->get_user();
1259 my $vmid = $param->{vmid
};
1260 my $node = $param->{node
};
1261 my $websocket = $param->{websocket
};
1263 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1265 my $authpath = "/vms/$vmid";
1267 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1269 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1272 my ($remip, $family);
1275 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1276 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1277 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1278 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1280 $family = PVE
::Tools
::get_host_address_family
($node);
1283 my $port = PVE
::Tools
::next_vnc_port
($family);
1290 syslog
('info', "starting vnc proxy $upid\n");
1294 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1296 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1298 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1299 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1300 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1301 '-timeout', $timeout, '-authpath', $authpath,
1302 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1305 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1307 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1309 my $qmstr = join(' ', @$qmcmd);
1311 # also redirect stderr (else we get RFB protocol errors)
1312 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1315 PVE
::Tools
::run_command
($cmd);
1320 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1322 PVE
::Tools
::wait_for_vnc_port
($port);
1333 __PACKAGE__-
>register_method({
1334 name
=> 'vncwebsocket',
1335 path
=> '{vmid}/vncwebsocket',
1338 description
=> "You also need to pass a valid ticket (vncticket).",
1339 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1341 description
=> "Opens a weksocket for VNC traffic.",
1343 additionalProperties
=> 0,
1345 node
=> get_standard_option
('pve-node'),
1346 vmid
=> get_standard_option
('pve-vmid'),
1348 description
=> "Ticket from previous call to vncproxy.",
1353 description
=> "Port number returned by previous vncproxy call.",
1363 port
=> { type
=> 'string' },
1369 my $rpcenv = PVE
::RPCEnvironment
::get
();
1371 my $authuser = $rpcenv->get_user();
1373 my $vmid = $param->{vmid
};
1374 my $node = $param->{node
};
1376 my $authpath = "/vms/$vmid";
1378 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1380 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1382 # Note: VNC ports are acessible from outside, so we do not gain any
1383 # security if we verify that $param->{port} belongs to VM $vmid. This
1384 # check is done by verifying the VNC ticket (inside VNC protocol).
1386 my $port = $param->{port
};
1388 return { port
=> $port };
1391 __PACKAGE__-
>register_method({
1392 name
=> 'spiceproxy',
1393 path
=> '{vmid}/spiceproxy',
1398 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1400 description
=> "Returns a SPICE configuration to connect to the VM.",
1402 additionalProperties
=> 0,
1404 node
=> get_standard_option
('pve-node'),
1405 vmid
=> get_standard_option
('pve-vmid'),
1406 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1409 returns
=> get_standard_option
('remote-viewer-config'),
1413 my $rpcenv = PVE
::RPCEnvironment
::get
();
1415 my $authuser = $rpcenv->get_user();
1417 my $vmid = $param->{vmid
};
1418 my $node = $param->{node
};
1419 my $proxy = $param->{proxy
};
1421 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1422 my $title = "VM $vmid";
1423 $title .= " - ". $conf->{name
} if $conf->{name
};
1425 my $port = PVE
::QemuServer
::spice_port
($vmid);
1427 my ($ticket, undef, $remote_viewer_config) =
1428 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1430 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1431 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1433 return $remote_viewer_config;
1436 __PACKAGE__-
>register_method({
1438 path
=> '{vmid}/status',
1441 description
=> "Directory index",
1446 additionalProperties
=> 0,
1448 node
=> get_standard_option
('pve-node'),
1449 vmid
=> get_standard_option
('pve-vmid'),
1457 subdir
=> { type
=> 'string' },
1460 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1466 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1469 { subdir
=> 'current' },
1470 { subdir
=> 'start' },
1471 { subdir
=> 'stop' },
1477 __PACKAGE__-
>register_method({
1478 name
=> 'vm_status',
1479 path
=> '{vmid}/status/current',
1482 protected
=> 1, # qemu pid files are only readable by root
1483 description
=> "Get virtual machine status.",
1485 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1488 additionalProperties
=> 0,
1490 node
=> get_standard_option
('pve-node'),
1491 vmid
=> get_standard_option
('pve-vmid'),
1494 returns
=> { type
=> 'object' },
1499 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1501 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1502 my $status = $vmstatus->{$param->{vmid
}};
1504 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1506 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1511 __PACKAGE__-
>register_method({
1513 path
=> '{vmid}/status/start',
1517 description
=> "Start virtual machine.",
1519 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1522 additionalProperties
=> 0,
1524 node
=> get_standard_option
('pve-node'),
1525 vmid
=> get_standard_option
('pve-vmid'),
1526 skiplock
=> get_standard_option
('skiplock'),
1527 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1528 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1529 machine
=> get_standard_option
('pve-qm-machine'),
1538 my $rpcenv = PVE
::RPCEnvironment
::get
();
1540 my $authuser = $rpcenv->get_user();
1542 my $node = extract_param
($param, 'node');
1544 my $vmid = extract_param
($param, 'vmid');
1546 my $machine = extract_param
($param, 'machine');
1548 my $stateuri = extract_param
($param, 'stateuri');
1549 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1550 if $stateuri && $authuser ne 'root@pam';
1552 my $skiplock = extract_param
($param, 'skiplock');
1553 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1554 if $skiplock && $authuser ne 'root@pam';
1556 my $migratedfrom = extract_param
($param, 'migratedfrom');
1557 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1558 if $migratedfrom && $authuser ne 'root@pam';
1560 # read spice ticket from STDIN
1562 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1563 if (defined(my $line = <>)) {
1565 $spice_ticket = $line;
1569 my $storecfg = PVE
::Storage
::config
();
1571 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1572 $rpcenv->{type
} ne 'ha') {
1577 my $service = "vm:$vmid";
1579 my $cmd = ['ha-manager', 'enable', $service];
1581 print "Executing HA start for VM $vmid\n";
1583 PVE
::Tools
::run_command
($cmd);
1588 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1595 syslog
('info', "start VM $vmid: $upid\n");
1597 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1598 $machine, $spice_ticket);
1603 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1607 __PACKAGE__-
>register_method({
1609 path
=> '{vmid}/status/stop',
1613 description
=> "Stop virtual machine.",
1615 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1618 additionalProperties
=> 0,
1620 node
=> get_standard_option
('pve-node'),
1621 vmid
=> get_standard_option
('pve-vmid'),
1622 skiplock
=> get_standard_option
('skiplock'),
1623 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1625 description
=> "Wait maximal timeout seconds.",
1631 description
=> "Do not decativate storage volumes.",
1644 my $rpcenv = PVE
::RPCEnvironment
::get
();
1646 my $authuser = $rpcenv->get_user();
1648 my $node = extract_param
($param, 'node');
1650 my $vmid = extract_param
($param, 'vmid');
1652 my $skiplock = extract_param
($param, 'skiplock');
1653 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1654 if $skiplock && $authuser ne 'root@pam';
1656 my $keepActive = extract_param
($param, 'keepActive');
1657 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1658 if $keepActive && $authuser ne 'root@pam';
1660 my $migratedfrom = extract_param
($param, 'migratedfrom');
1661 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1662 if $migratedfrom && $authuser ne 'root@pam';
1665 my $storecfg = PVE
::Storage
::config
();
1667 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1672 my $service = "vm:$vmid";
1674 my $cmd = ['ha-manager', 'disable', $service];
1676 print "Executing HA stop for VM $vmid\n";
1678 PVE
::Tools
::run_command
($cmd);
1683 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1689 syslog
('info', "stop VM $vmid: $upid\n");
1691 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1692 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1697 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1701 __PACKAGE__-
>register_method({
1703 path
=> '{vmid}/status/reset',
1707 description
=> "Reset virtual machine.",
1709 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1712 additionalProperties
=> 0,
1714 node
=> get_standard_option
('pve-node'),
1715 vmid
=> get_standard_option
('pve-vmid'),
1716 skiplock
=> get_standard_option
('skiplock'),
1725 my $rpcenv = PVE
::RPCEnvironment
::get
();
1727 my $authuser = $rpcenv->get_user();
1729 my $node = extract_param
($param, 'node');
1731 my $vmid = extract_param
($param, 'vmid');
1733 my $skiplock = extract_param
($param, 'skiplock');
1734 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1735 if $skiplock && $authuser ne 'root@pam';
1737 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1742 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1747 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1750 __PACKAGE__-
>register_method({
1751 name
=> 'vm_shutdown',
1752 path
=> '{vmid}/status/shutdown',
1756 description
=> "Shutdown virtual machine.",
1758 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1761 additionalProperties
=> 0,
1763 node
=> get_standard_option
('pve-node'),
1764 vmid
=> get_standard_option
('pve-vmid'),
1765 skiplock
=> get_standard_option
('skiplock'),
1767 description
=> "Wait maximal timeout seconds.",
1773 description
=> "Make sure the VM stops.",
1779 description
=> "Do not decativate storage volumes.",
1792 my $rpcenv = PVE
::RPCEnvironment
::get
();
1794 my $authuser = $rpcenv->get_user();
1796 my $node = extract_param
($param, 'node');
1798 my $vmid = extract_param
($param, 'vmid');
1800 my $skiplock = extract_param
($param, 'skiplock');
1801 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1802 if $skiplock && $authuser ne 'root@pam';
1804 my $keepActive = extract_param
($param, 'keepActive');
1805 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1806 if $keepActive && $authuser ne 'root@pam';
1808 my $storecfg = PVE
::Storage
::config
();
1813 syslog
('info', "shutdown VM $vmid: $upid\n");
1815 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1816 1, $param->{forceStop
}, $keepActive);
1821 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1824 __PACKAGE__-
>register_method({
1825 name
=> 'vm_suspend',
1826 path
=> '{vmid}/status/suspend',
1830 description
=> "Suspend virtual machine.",
1832 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1835 additionalProperties
=> 0,
1837 node
=> get_standard_option
('pve-node'),
1838 vmid
=> get_standard_option
('pve-vmid'),
1839 skiplock
=> get_standard_option
('skiplock'),
1848 my $rpcenv = PVE
::RPCEnvironment
::get
();
1850 my $authuser = $rpcenv->get_user();
1852 my $node = extract_param
($param, 'node');
1854 my $vmid = extract_param
($param, 'vmid');
1856 my $skiplock = extract_param
($param, 'skiplock');
1857 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1858 if $skiplock && $authuser ne 'root@pam';
1860 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1865 syslog
('info', "suspend VM $vmid: $upid\n");
1867 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1872 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1875 __PACKAGE__-
>register_method({
1876 name
=> 'vm_resume',
1877 path
=> '{vmid}/status/resume',
1881 description
=> "Resume virtual machine.",
1883 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1886 additionalProperties
=> 0,
1888 node
=> get_standard_option
('pve-node'),
1889 vmid
=> get_standard_option
('pve-vmid'),
1890 skiplock
=> get_standard_option
('skiplock'),
1891 nocheck
=> { type
=> 'boolean', optional
=> 1 },
1901 my $rpcenv = PVE
::RPCEnvironment
::get
();
1903 my $authuser = $rpcenv->get_user();
1905 my $node = extract_param
($param, 'node');
1907 my $vmid = extract_param
($param, 'vmid');
1909 my $skiplock = extract_param
($param, 'skiplock');
1910 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1911 if $skiplock && $authuser ne 'root@pam';
1913 my $nocheck = extract_param
($param, 'nocheck');
1915 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
1920 syslog
('info', "resume VM $vmid: $upid\n");
1922 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
1927 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1930 __PACKAGE__-
>register_method({
1931 name
=> 'vm_sendkey',
1932 path
=> '{vmid}/sendkey',
1936 description
=> "Send key event to virtual machine.",
1938 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1941 additionalProperties
=> 0,
1943 node
=> get_standard_option
('pve-node'),
1944 vmid
=> get_standard_option
('pve-vmid'),
1945 skiplock
=> get_standard_option
('skiplock'),
1947 description
=> "The key (qemu monitor encoding).",
1952 returns
=> { type
=> 'null'},
1956 my $rpcenv = PVE
::RPCEnvironment
::get
();
1958 my $authuser = $rpcenv->get_user();
1960 my $node = extract_param
($param, 'node');
1962 my $vmid = extract_param
($param, 'vmid');
1964 my $skiplock = extract_param
($param, 'skiplock');
1965 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1966 if $skiplock && $authuser ne 'root@pam';
1968 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1973 __PACKAGE__-
>register_method({
1974 name
=> 'vm_feature',
1975 path
=> '{vmid}/feature',
1979 description
=> "Check if feature for virtual machine is available.",
1981 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1984 additionalProperties
=> 0,
1986 node
=> get_standard_option
('pve-node'),
1987 vmid
=> get_standard_option
('pve-vmid'),
1989 description
=> "Feature to check.",
1991 enum
=> [ 'snapshot', 'clone', 'copy' ],
1993 snapname
=> get_standard_option
('pve-snapshot-name', {
2001 hasFeature
=> { type
=> 'boolean' },
2004 items
=> { type
=> 'string' },
2011 my $node = extract_param
($param, 'node');
2013 my $vmid = extract_param
($param, 'vmid');
2015 my $snapname = extract_param
($param, 'snapname');
2017 my $feature = extract_param
($param, 'feature');
2019 my $running = PVE
::QemuServer
::check_running
($vmid);
2021 my $conf = PVE
::QemuServer
::load_config
($vmid);
2024 my $snap = $conf->{snapshots
}->{$snapname};
2025 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2028 my $storecfg = PVE
::Storage
::config
();
2030 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2031 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2034 hasFeature
=> $hasFeature,
2035 nodes
=> [ keys %$nodelist ],
2039 __PACKAGE__-
>register_method({
2041 path
=> '{vmid}/clone',
2045 description
=> "Create a copy of virtual machine/template.",
2047 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2048 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2049 "'Datastore.AllocateSpace' on any used storage.",
2052 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2054 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2055 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2060 additionalProperties
=> 0,
2062 node
=> get_standard_option
('pve-node'),
2063 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2064 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2067 type
=> 'string', format
=> 'dns-name',
2068 description
=> "Set a name for the new VM.",
2073 description
=> "Description for the new VM.",
2077 type
=> 'string', format
=> 'pve-poolid',
2078 description
=> "Add the new VM to the specified pool.",
2080 snapname
=> get_standard_option
('pve-snapshot-name', {
2083 storage
=> get_standard_option
('pve-storage-id', {
2084 description
=> "Target storage for full clone.",
2089 description
=> "Target format for file storage.",
2093 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2098 description
=> "Create a full copy of all disk. This is always done when " .
2099 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2102 target
=> get_standard_option
('pve-node', {
2103 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2114 my $rpcenv = PVE
::RPCEnvironment
::get
();
2116 my $authuser = $rpcenv->get_user();
2118 my $node = extract_param
($param, 'node');
2120 my $vmid = extract_param
($param, 'vmid');
2122 my $newid = extract_param
($param, 'newid');
2124 my $pool = extract_param
($param, 'pool');
2126 if (defined($pool)) {
2127 $rpcenv->check_pool_exist($pool);
2130 my $snapname = extract_param
($param, 'snapname');
2132 my $storage = extract_param
($param, 'storage');
2134 my $format = extract_param
($param, 'format');
2136 my $target = extract_param
($param, 'target');
2138 my $localnode = PVE
::INotify
::nodename
();
2140 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2142 PVE
::Cluster
::check_node_exists
($target) if $target;
2144 my $storecfg = PVE
::Storage
::config
();
2147 # check if storage is enabled on local node
2148 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2150 # check if storage is available on target node
2151 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2152 # clone only works if target storage is shared
2153 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2154 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2158 PVE
::Cluster
::check_cfs_quorum
();
2160 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2162 # exclusive lock if VM is running - else shared lock is enough;
2163 my $shared_lock = $running ?
0 : 1;
2167 # do all tests after lock
2168 # we also try to do all tests before we fork the worker
2170 my $conf = PVE
::QemuServer
::load_config
($vmid);
2172 PVE
::QemuServer
::check_lock
($conf);
2174 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2176 die "unexpected state change\n" if $verify_running != $running;
2178 die "snapshot '$snapname' does not exist\n"
2179 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2181 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2183 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2185 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2187 my $conffile = PVE
::QemuServer
::config_file
($newid);
2189 die "unable to create VM $newid: config file already exists\n"
2192 my $newconf = { lock => 'clone' };
2197 foreach my $opt (keys %$oldconf) {
2198 my $value = $oldconf->{$opt};
2200 # do not copy snapshot related info
2201 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2202 $opt eq 'vmstate' || $opt eq 'snapstate';
2204 # no need to copy unused images, because VMID(owner) changes anyways
2205 next if $opt =~ m/^unused\d+$/;
2207 # always change MAC! address
2208 if ($opt =~ m/^net(\d+)$/) {
2209 my $net = PVE
::QemuServer
::parse_net
($value);
2210 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2211 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2212 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2213 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2214 die "unable to parse drive options for '$opt'\n" if !$drive;
2215 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2216 $newconf->{$opt} = $value; # simply copy configuration
2218 if ($param->{full
}) {
2219 die "Full clone feature is not available"
2220 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2221 $fullclone->{$opt} = 1;
2223 # not full means clone instead of copy
2224 die "Linked clone feature is not available"
2225 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2227 $drives->{$opt} = $drive;
2228 push @$vollist, $drive->{file
};
2231 # copy everything else
2232 $newconf->{$opt} = $value;
2236 # auto generate a new uuid
2237 my ($uuid, $uuid_str);
2238 UUID
::generate
($uuid);
2239 UUID
::unparse
($uuid, $uuid_str);
2240 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2241 $smbios1->{uuid
} = $uuid_str;
2242 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2244 delete $newconf->{template
};
2246 if ($param->{name
}) {
2247 $newconf->{name
} = $param->{name
};
2249 if ($oldconf->{name
}) {
2250 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2252 $newconf->{name
} = "Copy-of-VM-$vmid";
2256 if ($param->{description
}) {
2257 $newconf->{description
} = $param->{description
};
2260 # create empty/temp config - this fails if VM already exists on other node
2261 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2266 my $newvollist = [];
2269 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2271 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2273 foreach my $opt (keys %$drives) {
2274 my $drive = $drives->{$opt};
2276 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2277 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2279 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2281 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2284 delete $newconf->{lock};
2285 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2288 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2289 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2291 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2292 die "Failed to move config to node '$target' - rename failed: $!\n"
2293 if !rename($conffile, $newconffile);
2296 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2301 sleep 1; # some storage like rbd need to wait before release volume - really?
2303 foreach my $volid (@$newvollist) {
2304 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2307 die "clone failed: $err";
2313 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2315 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2318 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2319 # Aquire exclusive lock lock for $newid
2320 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2325 __PACKAGE__-
>register_method({
2326 name
=> 'move_vm_disk',
2327 path
=> '{vmid}/move_disk',
2331 description
=> "Move volume to different storage.",
2333 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2334 "and 'Datastore.AllocateSpace' permissions on the storage.",
2337 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2338 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2342 additionalProperties
=> 0,
2344 node
=> get_standard_option
('pve-node'),
2345 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2348 description
=> "The disk you want to move.",
2349 enum
=> [ PVE
::QemuServer
::disknames
() ],
2351 storage
=> get_standard_option
('pve-storage-id', {
2352 description
=> "Target storage.",
2353 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2357 description
=> "Target Format.",
2358 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2363 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2369 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2377 description
=> "the task ID.",
2382 my $rpcenv = PVE
::RPCEnvironment
::get
();
2384 my $authuser = $rpcenv->get_user();
2386 my $node = extract_param
($param, 'node');
2388 my $vmid = extract_param
($param, 'vmid');
2390 my $digest = extract_param
($param, 'digest');
2392 my $disk = extract_param
($param, 'disk');
2394 my $storeid = extract_param
($param, 'storage');
2396 my $format = extract_param
($param, 'format');
2398 my $storecfg = PVE
::Storage
::config
();
2400 my $updatefn = sub {
2402 my $conf = PVE
::QemuServer
::load_config
($vmid);
2404 die "checksum missmatch (file change by other user?)\n"
2405 if $digest && $digest ne $conf->{digest
};
2407 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2409 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2411 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2413 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2416 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2417 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2421 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2422 (!$format || !$oldfmt || $oldfmt eq $format);
2424 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2426 my $running = PVE
::QemuServer
::check_running
($vmid);
2428 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2432 my $newvollist = [];
2435 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2437 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2438 $vmid, $storeid, $format, 1, $newvollist);
2440 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2442 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2444 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2447 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2448 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2455 foreach my $volid (@$newvollist) {
2456 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2459 die "storage migration failed: $err";
2462 if ($param->{delete}) {
2463 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2464 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2465 if ($used_paths->{$path}){
2466 warn "volume $old_volid have snapshots. Can't delete it\n";
2467 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2468 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2470 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2476 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2479 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2482 __PACKAGE__-
>register_method({
2483 name
=> 'migrate_vm',
2484 path
=> '{vmid}/migrate',
2488 description
=> "Migrate virtual machine. Creates a new migration task.",
2490 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2493 additionalProperties
=> 0,
2495 node
=> get_standard_option
('pve-node'),
2496 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2497 target
=> get_standard_option
('pve-node', {
2498 description
=> "Target node.",
2499 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2503 description
=> "Use online/live migration.",
2508 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2515 description
=> "the task ID.",
2520 my $rpcenv = PVE
::RPCEnvironment
::get
();
2522 my $authuser = $rpcenv->get_user();
2524 my $target = extract_param
($param, 'target');
2526 my $localnode = PVE
::INotify
::nodename
();
2527 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2529 PVE
::Cluster
::check_cfs_quorum
();
2531 PVE
::Cluster
::check_node_exists
($target);
2533 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2535 my $vmid = extract_param
($param, 'vmid');
2537 raise_param_exc
({ force
=> "Only root may use this option." })
2538 if $param->{force
} && $authuser ne 'root@pam';
2541 my $conf = PVE
::QemuServer
::load_config
($vmid);
2543 # try to detect errors early
2545 PVE
::QemuServer
::check_lock
($conf);
2547 if (PVE
::QemuServer
::check_running
($vmid)) {
2548 die "cant migrate running VM without --online\n"
2549 if !$param->{online
};
2552 my $storecfg = PVE
::Storage
::config
();
2553 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2555 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2560 my $service = "vm:$vmid";
2562 my $cmd = ['ha-manager', 'migrate', $service, $target];
2564 print "Executing HA migrate for VM $vmid to node $target\n";
2566 PVE
::Tools
::run_command
($cmd);
2571 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2578 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2581 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2586 __PACKAGE__-
>register_method({
2588 path
=> '{vmid}/monitor',
2592 description
=> "Execute Qemu monitor commands.",
2594 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2597 additionalProperties
=> 0,
2599 node
=> get_standard_option
('pve-node'),
2600 vmid
=> get_standard_option
('pve-vmid'),
2603 description
=> "The monitor command.",
2607 returns
=> { type
=> 'string'},
2611 my $vmid = $param->{vmid
};
2613 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2617 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2619 $res = "ERROR: $@" if $@;
2624 __PACKAGE__-
>register_method({
2625 name
=> 'resize_vm',
2626 path
=> '{vmid}/resize',
2630 description
=> "Extend volume size.",
2632 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2635 additionalProperties
=> 0,
2637 node
=> get_standard_option
('pve-node'),
2638 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2639 skiplock
=> get_standard_option
('skiplock'),
2642 description
=> "The disk you want to resize.",
2643 enum
=> [PVE
::QemuServer
::disknames
()],
2647 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2648 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.",
2652 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2658 returns
=> { type
=> 'null'},
2662 my $rpcenv = PVE
::RPCEnvironment
::get
();
2664 my $authuser = $rpcenv->get_user();
2666 my $node = extract_param
($param, 'node');
2668 my $vmid = extract_param
($param, 'vmid');
2670 my $digest = extract_param
($param, 'digest');
2672 my $disk = extract_param
($param, 'disk');
2674 my $sizestr = extract_param
($param, 'size');
2676 my $skiplock = extract_param
($param, 'skiplock');
2677 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2678 if $skiplock && $authuser ne 'root@pam';
2680 my $storecfg = PVE
::Storage
::config
();
2682 my $updatefn = sub {
2684 my $conf = PVE
::QemuServer
::load_config
($vmid);
2686 die "checksum missmatch (file change by other user?)\n"
2687 if $digest && $digest ne $conf->{digest
};
2688 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2690 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2692 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2694 my (undef, undef, undef, undef, undef, undef, $format) =
2695 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2697 die "can't resize volume: $disk if snapshot exists\n"
2698 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2700 my $volid = $drive->{file
};
2702 die "disk '$disk' has no associated volume\n" if !$volid;
2704 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2706 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2708 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2710 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2712 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2713 my ($ext, $newsize, $unit) = ($1, $2, $4);
2716 $newsize = $newsize * 1024;
2717 } elsif ($unit eq 'M') {
2718 $newsize = $newsize * 1024 * 1024;
2719 } elsif ($unit eq 'G') {
2720 $newsize = $newsize * 1024 * 1024 * 1024;
2721 } elsif ($unit eq 'T') {
2722 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2725 $newsize += $size if $ext;
2726 $newsize = int($newsize);
2728 die "unable to skrink disk size\n" if $newsize < $size;
2730 return if $size == $newsize;
2732 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2734 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2736 $drive->{size
} = $newsize;
2737 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2739 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2742 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2746 __PACKAGE__-
>register_method({
2747 name
=> 'snapshot_list',
2748 path
=> '{vmid}/snapshot',
2750 description
=> "List all snapshots.",
2752 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2755 protected
=> 1, # qemu pid files are only readable by root
2757 additionalProperties
=> 0,
2759 vmid
=> get_standard_option
('pve-vmid'),
2760 node
=> get_standard_option
('pve-node'),
2769 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2774 my $vmid = $param->{vmid
};
2776 my $conf = PVE
::QemuServer
::load_config
($vmid);
2777 my $snaphash = $conf->{snapshots
} || {};
2781 foreach my $name (keys %$snaphash) {
2782 my $d = $snaphash->{$name};
2785 snaptime
=> $d->{snaptime
} || 0,
2786 vmstate
=> $d->{vmstate
} ?
1 : 0,
2787 description
=> $d->{description
} || '',
2789 $item->{parent
} = $d->{parent
} if $d->{parent
};
2790 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2794 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2795 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2796 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2798 push @$res, $current;
2803 __PACKAGE__-
>register_method({
2805 path
=> '{vmid}/snapshot',
2809 description
=> "Snapshot a VM.",
2811 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2814 additionalProperties
=> 0,
2816 node
=> get_standard_option
('pve-node'),
2817 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2818 snapname
=> get_standard_option
('pve-snapshot-name'),
2822 description
=> "Save the vmstate",
2827 description
=> "A textual description or comment.",
2833 description
=> "the task ID.",
2838 my $rpcenv = PVE
::RPCEnvironment
::get
();
2840 my $authuser = $rpcenv->get_user();
2842 my $node = extract_param
($param, 'node');
2844 my $vmid = extract_param
($param, 'vmid');
2846 my $snapname = extract_param
($param, 'snapname');
2848 die "unable to use snapshot name 'current' (reserved name)\n"
2849 if $snapname eq 'current';
2852 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2853 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2854 $param->{description
});
2857 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2860 __PACKAGE__-
>register_method({
2861 name
=> 'snapshot_cmd_idx',
2862 path
=> '{vmid}/snapshot/{snapname}',
2869 additionalProperties
=> 0,
2871 vmid
=> get_standard_option
('pve-vmid'),
2872 node
=> get_standard_option
('pve-node'),
2873 snapname
=> get_standard_option
('pve-snapshot-name'),
2882 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2889 push @$res, { cmd
=> 'rollback' };
2890 push @$res, { cmd
=> 'config' };
2895 __PACKAGE__-
>register_method({
2896 name
=> 'update_snapshot_config',
2897 path
=> '{vmid}/snapshot/{snapname}/config',
2901 description
=> "Update snapshot metadata.",
2903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2906 additionalProperties
=> 0,
2908 node
=> get_standard_option
('pve-node'),
2909 vmid
=> get_standard_option
('pve-vmid'),
2910 snapname
=> get_standard_option
('pve-snapshot-name'),
2914 description
=> "A textual description or comment.",
2918 returns
=> { type
=> 'null' },
2922 my $rpcenv = PVE
::RPCEnvironment
::get
();
2924 my $authuser = $rpcenv->get_user();
2926 my $vmid = extract_param
($param, 'vmid');
2928 my $snapname = extract_param
($param, 'snapname');
2930 return undef if !defined($param->{description
});
2932 my $updatefn = sub {
2934 my $conf = PVE
::QemuServer
::load_config
($vmid);
2936 PVE
::QemuServer
::check_lock
($conf);
2938 my $snap = $conf->{snapshots
}->{$snapname};
2940 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2942 $snap->{description
} = $param->{description
} if defined($param->{description
});
2944 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2947 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2952 __PACKAGE__-
>register_method({
2953 name
=> 'get_snapshot_config',
2954 path
=> '{vmid}/snapshot/{snapname}/config',
2957 description
=> "Get snapshot configuration",
2959 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2962 additionalProperties
=> 0,
2964 node
=> get_standard_option
('pve-node'),
2965 vmid
=> get_standard_option
('pve-vmid'),
2966 snapname
=> get_standard_option
('pve-snapshot-name'),
2969 returns
=> { type
=> "object" },
2973 my $rpcenv = PVE
::RPCEnvironment
::get
();
2975 my $authuser = $rpcenv->get_user();
2977 my $vmid = extract_param
($param, 'vmid');
2979 my $snapname = extract_param
($param, 'snapname');
2981 my $conf = PVE
::QemuServer
::load_config
($vmid);
2983 my $snap = $conf->{snapshots
}->{$snapname};
2985 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2990 __PACKAGE__-
>register_method({
2992 path
=> '{vmid}/snapshot/{snapname}/rollback',
2996 description
=> "Rollback VM state to specified snapshot.",
2998 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3001 additionalProperties
=> 0,
3003 node
=> get_standard_option
('pve-node'),
3004 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3005 snapname
=> get_standard_option
('pve-snapshot-name'),
3010 description
=> "the task ID.",
3015 my $rpcenv = PVE
::RPCEnvironment
::get
();
3017 my $authuser = $rpcenv->get_user();
3019 my $node = extract_param
($param, 'node');
3021 my $vmid = extract_param
($param, 'vmid');
3023 my $snapname = extract_param
($param, 'snapname');
3026 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3027 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3030 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3033 __PACKAGE__-
>register_method({
3034 name
=> 'delsnapshot',
3035 path
=> '{vmid}/snapshot/{snapname}',
3039 description
=> "Delete a VM snapshot.",
3041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3044 additionalProperties
=> 0,
3046 node
=> get_standard_option
('pve-node'),
3047 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3048 snapname
=> get_standard_option
('pve-snapshot-name'),
3052 description
=> "For removal from config file, even if removing disk snapshots fails.",
3058 description
=> "the task ID.",
3063 my $rpcenv = PVE
::RPCEnvironment
::get
();
3065 my $authuser = $rpcenv->get_user();
3067 my $node = extract_param
($param, 'node');
3069 my $vmid = extract_param
($param, 'vmid');
3071 my $snapname = extract_param
($param, 'snapname');
3074 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3075 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3078 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3081 __PACKAGE__-
>register_method({
3083 path
=> '{vmid}/template',
3087 description
=> "Create a Template.",
3089 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3090 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3093 additionalProperties
=> 0,
3095 node
=> get_standard_option
('pve-node'),
3096 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3100 description
=> "If you want to convert only 1 disk to base image.",
3101 enum
=> [PVE
::QemuServer
::disknames
()],
3106 returns
=> { type
=> 'null'},
3110 my $rpcenv = PVE
::RPCEnvironment
::get
();
3112 my $authuser = $rpcenv->get_user();
3114 my $node = extract_param
($param, 'node');
3116 my $vmid = extract_param
($param, 'vmid');
3118 my $disk = extract_param
($param, 'disk');
3120 my $updatefn = sub {
3122 my $conf = PVE
::QemuServer
::load_config
($vmid);
3124 PVE
::QemuServer
::check_lock
($conf);
3126 die "unable to create template, because VM contains snapshots\n"
3127 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3129 die "you can't convert a template to a template\n"
3130 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3132 die "you can't convert a VM to template if VM is running\n"
3133 if PVE
::QemuServer
::check_running
($vmid);
3136 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3139 $conf->{template
} = 1;
3140 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3142 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3145 PVE
::QemuServer
::lock_config
($vmid, $updatefn);