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'),
1899 my $rpcenv = PVE
::RPCEnvironment
::get
();
1901 my $authuser = $rpcenv->get_user();
1903 my $node = extract_param
($param, 'node');
1905 my $vmid = extract_param
($param, 'vmid');
1907 my $skiplock = extract_param
($param, 'skiplock');
1908 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1909 if $skiplock && $authuser ne 'root@pam';
1911 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1916 syslog
('info', "resume VM $vmid: $upid\n");
1918 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1923 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1926 __PACKAGE__-
>register_method({
1927 name
=> 'vm_sendkey',
1928 path
=> '{vmid}/sendkey',
1932 description
=> "Send key event to virtual machine.",
1934 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1937 additionalProperties
=> 0,
1939 node
=> get_standard_option
('pve-node'),
1940 vmid
=> get_standard_option
('pve-vmid'),
1941 skiplock
=> get_standard_option
('skiplock'),
1943 description
=> "The key (qemu monitor encoding).",
1948 returns
=> { type
=> 'null'},
1952 my $rpcenv = PVE
::RPCEnvironment
::get
();
1954 my $authuser = $rpcenv->get_user();
1956 my $node = extract_param
($param, 'node');
1958 my $vmid = extract_param
($param, 'vmid');
1960 my $skiplock = extract_param
($param, 'skiplock');
1961 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1962 if $skiplock && $authuser ne 'root@pam';
1964 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1969 __PACKAGE__-
>register_method({
1970 name
=> 'vm_feature',
1971 path
=> '{vmid}/feature',
1975 description
=> "Check if feature for virtual machine is available.",
1977 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1980 additionalProperties
=> 0,
1982 node
=> get_standard_option
('pve-node'),
1983 vmid
=> get_standard_option
('pve-vmid'),
1985 description
=> "Feature to check.",
1987 enum
=> [ 'snapshot', 'clone', 'copy' ],
1989 snapname
=> get_standard_option
('pve-snapshot-name', {
1997 hasFeature
=> { type
=> 'boolean' },
2000 items
=> { type
=> 'string' },
2007 my $node = extract_param
($param, 'node');
2009 my $vmid = extract_param
($param, 'vmid');
2011 my $snapname = extract_param
($param, 'snapname');
2013 my $feature = extract_param
($param, 'feature');
2015 my $running = PVE
::QemuServer
::check_running
($vmid);
2017 my $conf = PVE
::QemuServer
::load_config
($vmid);
2020 my $snap = $conf->{snapshots
}->{$snapname};
2021 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2024 my $storecfg = PVE
::Storage
::config
();
2026 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2027 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2030 hasFeature
=> $hasFeature,
2031 nodes
=> [ keys %$nodelist ],
2035 __PACKAGE__-
>register_method({
2037 path
=> '{vmid}/clone',
2041 description
=> "Create a copy of virtual machine/template.",
2043 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2044 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2045 "'Datastore.AllocateSpace' on any used storage.",
2048 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2050 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2051 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2056 additionalProperties
=> 0,
2058 node
=> get_standard_option
('pve-node'),
2059 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2060 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2063 type
=> 'string', format
=> 'dns-name',
2064 description
=> "Set a name for the new VM.",
2069 description
=> "Description for the new VM.",
2073 type
=> 'string', format
=> 'pve-poolid',
2074 description
=> "Add the new VM to the specified pool.",
2076 snapname
=> get_standard_option
('pve-snapshot-name', {
2079 storage
=> get_standard_option
('pve-storage-id', {
2080 description
=> "Target storage for full clone.",
2085 description
=> "Target format for file storage.",
2089 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2094 description
=> "Create a full copy of all disk. This is always done when " .
2095 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2098 target
=> get_standard_option
('pve-node', {
2099 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2110 my $rpcenv = PVE
::RPCEnvironment
::get
();
2112 my $authuser = $rpcenv->get_user();
2114 my $node = extract_param
($param, 'node');
2116 my $vmid = extract_param
($param, 'vmid');
2118 my $newid = extract_param
($param, 'newid');
2120 my $pool = extract_param
($param, 'pool');
2122 if (defined($pool)) {
2123 $rpcenv->check_pool_exist($pool);
2126 my $snapname = extract_param
($param, 'snapname');
2128 my $storage = extract_param
($param, 'storage');
2130 my $format = extract_param
($param, 'format');
2132 my $target = extract_param
($param, 'target');
2134 my $localnode = PVE
::INotify
::nodename
();
2136 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2138 PVE
::Cluster
::check_node_exists
($target) if $target;
2140 my $storecfg = PVE
::Storage
::config
();
2143 # check if storage is enabled on local node
2144 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2146 # check if storage is available on target node
2147 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2148 # clone only works if target storage is shared
2149 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2150 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2154 PVE
::Cluster
::check_cfs_quorum
();
2156 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2158 # exclusive lock if VM is running - else shared lock is enough;
2159 my $shared_lock = $running ?
0 : 1;
2163 # do all tests after lock
2164 # we also try to do all tests before we fork the worker
2166 my $conf = PVE
::QemuServer
::load_config
($vmid);
2168 PVE
::QemuServer
::check_lock
($conf);
2170 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2172 die "unexpected state change\n" if $verify_running != $running;
2174 die "snapshot '$snapname' does not exist\n"
2175 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2177 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2179 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2181 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2183 my $conffile = PVE
::QemuServer
::config_file
($newid);
2185 die "unable to create VM $newid: config file already exists\n"
2188 my $newconf = { lock => 'clone' };
2192 foreach my $opt (keys %$oldconf) {
2193 my $value = $oldconf->{$opt};
2195 # do not copy snapshot related info
2196 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2197 $opt eq 'vmstate' || $opt eq 'snapstate';
2199 # no need to copy unused images, because VMID(owner) changes anyways
2200 next if $opt =~ m/^unused\d+$/;
2202 # always change MAC! address
2203 if ($opt =~ m/^net(\d+)$/) {
2204 my $net = PVE
::QemuServer
::parse_net
($value);
2205 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2206 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2207 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2208 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2209 die "unable to parse drive options for '$opt'\n" if !$drive;
2210 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2211 $newconf->{$opt} = $value; # simply copy configuration
2213 if ($param->{full
}) {
2214 die "Full clone feature is not available"
2215 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2218 # not full means clone instead of copy
2219 die "Linked clone feature is not available"
2220 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2222 $drives->{$opt} = $drive;
2223 push @$vollist, $drive->{file
};
2226 # copy everything else
2227 $newconf->{$opt} = $value;
2231 # auto generate a new uuid
2232 my ($uuid, $uuid_str);
2233 UUID
::generate
($uuid);
2234 UUID
::unparse
($uuid, $uuid_str);
2235 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2236 $smbios1->{uuid
} = $uuid_str;
2237 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2239 delete $newconf->{template
};
2241 if ($param->{name
}) {
2242 $newconf->{name
} = $param->{name
};
2244 if ($oldconf->{name
}) {
2245 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2247 $newconf->{name
} = "Copy-of-VM-$vmid";
2251 if ($param->{description
}) {
2252 $newconf->{description
} = $param->{description
};
2255 # create empty/temp config - this fails if VM already exists on other node
2256 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2261 my $newvollist = [];
2264 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2266 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2268 foreach my $opt (keys %$drives) {
2269 my $drive = $drives->{$opt};
2271 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2272 $newid, $storage, $format, $drive->{full
}, $newvollist);
2274 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2276 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2279 delete $newconf->{lock};
2280 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2283 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2284 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2286 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2287 die "Failed to move config to node '$target' - rename failed: $!\n"
2288 if !rename($conffile, $newconffile);
2291 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2296 sleep 1; # some storage like rbd need to wait before release volume - really?
2298 foreach my $volid (@$newvollist) {
2299 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2302 die "clone failed: $err";
2308 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2310 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2313 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2314 # Aquire exclusive lock lock for $newid
2315 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2320 __PACKAGE__-
>register_method({
2321 name
=> 'move_vm_disk',
2322 path
=> '{vmid}/move_disk',
2326 description
=> "Move volume to different storage.",
2328 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2329 "and 'Datastore.AllocateSpace' permissions on the storage.",
2332 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2333 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2337 additionalProperties
=> 0,
2339 node
=> get_standard_option
('pve-node'),
2340 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2343 description
=> "The disk you want to move.",
2344 enum
=> [ PVE
::QemuServer
::disknames
() ],
2346 storage
=> get_standard_option
('pve-storage-id', {
2347 description
=> "Target storage.",
2348 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2352 description
=> "Target Format.",
2353 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2358 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2364 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2372 description
=> "the task ID.",
2377 my $rpcenv = PVE
::RPCEnvironment
::get
();
2379 my $authuser = $rpcenv->get_user();
2381 my $node = extract_param
($param, 'node');
2383 my $vmid = extract_param
($param, 'vmid');
2385 my $digest = extract_param
($param, 'digest');
2387 my $disk = extract_param
($param, 'disk');
2389 my $storeid = extract_param
($param, 'storage');
2391 my $format = extract_param
($param, 'format');
2393 my $storecfg = PVE
::Storage
::config
();
2395 my $updatefn = sub {
2397 my $conf = PVE
::QemuServer
::load_config
($vmid);
2399 die "checksum missmatch (file change by other user?)\n"
2400 if $digest && $digest ne $conf->{digest
};
2402 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2404 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2406 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2408 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2411 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2412 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2416 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2417 (!$format || !$oldfmt || $oldfmt eq $format);
2419 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2421 my $running = PVE
::QemuServer
::check_running
($vmid);
2423 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2427 my $newvollist = [];
2430 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2432 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2433 $vmid, $storeid, $format, 1, $newvollist);
2435 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2437 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2439 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2442 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2443 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2450 foreach my $volid (@$newvollist) {
2451 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2454 die "storage migration failed: $err";
2457 if ($param->{delete}) {
2458 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2459 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2460 if ($used_paths->{$path}){
2461 warn "volume $old_volid have snapshots. Can't delete it\n";
2462 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2463 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2465 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2471 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2474 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2477 __PACKAGE__-
>register_method({
2478 name
=> 'migrate_vm',
2479 path
=> '{vmid}/migrate',
2483 description
=> "Migrate virtual machine. Creates a new migration task.",
2485 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2488 additionalProperties
=> 0,
2490 node
=> get_standard_option
('pve-node'),
2491 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2492 target
=> get_standard_option
('pve-node', {
2493 description
=> "Target node.",
2494 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2498 description
=> "Use online/live migration.",
2503 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2510 description
=> "the task ID.",
2515 my $rpcenv = PVE
::RPCEnvironment
::get
();
2517 my $authuser = $rpcenv->get_user();
2519 my $target = extract_param
($param, 'target');
2521 my $localnode = PVE
::INotify
::nodename
();
2522 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2524 PVE
::Cluster
::check_cfs_quorum
();
2526 PVE
::Cluster
::check_node_exists
($target);
2528 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2530 my $vmid = extract_param
($param, 'vmid');
2532 raise_param_exc
({ force
=> "Only root may use this option." })
2533 if $param->{force
} && $authuser ne 'root@pam';
2536 my $conf = PVE
::QemuServer
::load_config
($vmid);
2538 # try to detect errors early
2540 PVE
::QemuServer
::check_lock
($conf);
2542 if (PVE
::QemuServer
::check_running
($vmid)) {
2543 die "cant migrate running VM without --online\n"
2544 if !$param->{online
};
2547 my $storecfg = PVE
::Storage
::config
();
2548 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2550 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2555 my $service = "vm:$vmid";
2557 my $cmd = ['ha-manager', 'migrate', $service, $target];
2559 print "Executing HA migrate for VM $vmid to node $target\n";
2561 PVE
::Tools
::run_command
($cmd);
2566 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2573 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2576 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2581 __PACKAGE__-
>register_method({
2583 path
=> '{vmid}/monitor',
2587 description
=> "Execute Qemu monitor commands.",
2589 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2592 additionalProperties
=> 0,
2594 node
=> get_standard_option
('pve-node'),
2595 vmid
=> get_standard_option
('pve-vmid'),
2598 description
=> "The monitor command.",
2602 returns
=> { type
=> 'string'},
2606 my $vmid = $param->{vmid
};
2608 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2612 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2614 $res = "ERROR: $@" if $@;
2619 __PACKAGE__-
>register_method({
2620 name
=> 'resize_vm',
2621 path
=> '{vmid}/resize',
2625 description
=> "Extend volume size.",
2627 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2630 additionalProperties
=> 0,
2632 node
=> get_standard_option
('pve-node'),
2633 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2634 skiplock
=> get_standard_option
('skiplock'),
2637 description
=> "The disk you want to resize.",
2638 enum
=> [PVE
::QemuServer
::disknames
()],
2642 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2643 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.",
2647 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2653 returns
=> { type
=> 'null'},
2657 my $rpcenv = PVE
::RPCEnvironment
::get
();
2659 my $authuser = $rpcenv->get_user();
2661 my $node = extract_param
($param, 'node');
2663 my $vmid = extract_param
($param, 'vmid');
2665 my $digest = extract_param
($param, 'digest');
2667 my $disk = extract_param
($param, 'disk');
2669 my $sizestr = extract_param
($param, 'size');
2671 my $skiplock = extract_param
($param, 'skiplock');
2672 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2673 if $skiplock && $authuser ne 'root@pam';
2675 my $storecfg = PVE
::Storage
::config
();
2677 my $updatefn = sub {
2679 my $conf = PVE
::QemuServer
::load_config
($vmid);
2681 die "checksum missmatch (file change by other user?)\n"
2682 if $digest && $digest ne $conf->{digest
};
2683 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2685 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2687 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2689 my (undef, undef, undef, undef, undef, undef, $format) =
2690 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2692 die "can't resize volume: $disk if snapshot exists\n"
2693 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2695 my $volid = $drive->{file
};
2697 die "disk '$disk' has no associated volume\n" if !$volid;
2699 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2701 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2703 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2705 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2707 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2708 my ($ext, $newsize, $unit) = ($1, $2, $4);
2711 $newsize = $newsize * 1024;
2712 } elsif ($unit eq 'M') {
2713 $newsize = $newsize * 1024 * 1024;
2714 } elsif ($unit eq 'G') {
2715 $newsize = $newsize * 1024 * 1024 * 1024;
2716 } elsif ($unit eq 'T') {
2717 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2720 $newsize += $size if $ext;
2721 $newsize = int($newsize);
2723 die "unable to skrink disk size\n" if $newsize < $size;
2725 return if $size == $newsize;
2727 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2729 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2731 $drive->{size
} = $newsize;
2732 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2734 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2737 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2741 __PACKAGE__-
>register_method({
2742 name
=> 'snapshot_list',
2743 path
=> '{vmid}/snapshot',
2745 description
=> "List all snapshots.",
2747 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2750 protected
=> 1, # qemu pid files are only readable by root
2752 additionalProperties
=> 0,
2754 vmid
=> get_standard_option
('pve-vmid'),
2755 node
=> get_standard_option
('pve-node'),
2764 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2769 my $vmid = $param->{vmid
};
2771 my $conf = PVE
::QemuServer
::load_config
($vmid);
2772 my $snaphash = $conf->{snapshots
} || {};
2776 foreach my $name (keys %$snaphash) {
2777 my $d = $snaphash->{$name};
2780 snaptime
=> $d->{snaptime
} || 0,
2781 vmstate
=> $d->{vmstate
} ?
1 : 0,
2782 description
=> $d->{description
} || '',
2784 $item->{parent
} = $d->{parent
} if $d->{parent
};
2785 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2789 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2790 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2791 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2793 push @$res, $current;
2798 __PACKAGE__-
>register_method({
2800 path
=> '{vmid}/snapshot',
2804 description
=> "Snapshot a VM.",
2806 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2809 additionalProperties
=> 0,
2811 node
=> get_standard_option
('pve-node'),
2812 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2813 snapname
=> get_standard_option
('pve-snapshot-name'),
2817 description
=> "Save the vmstate",
2822 description
=> "A textual description or comment.",
2828 description
=> "the task ID.",
2833 my $rpcenv = PVE
::RPCEnvironment
::get
();
2835 my $authuser = $rpcenv->get_user();
2837 my $node = extract_param
($param, 'node');
2839 my $vmid = extract_param
($param, 'vmid');
2841 my $snapname = extract_param
($param, 'snapname');
2843 die "unable to use snapshot name 'current' (reserved name)\n"
2844 if $snapname eq 'current';
2847 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2848 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2849 $param->{description
});
2852 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2855 __PACKAGE__-
>register_method({
2856 name
=> 'snapshot_cmd_idx',
2857 path
=> '{vmid}/snapshot/{snapname}',
2864 additionalProperties
=> 0,
2866 vmid
=> get_standard_option
('pve-vmid'),
2867 node
=> get_standard_option
('pve-node'),
2868 snapname
=> get_standard_option
('pve-snapshot-name'),
2877 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2884 push @$res, { cmd
=> 'rollback' };
2885 push @$res, { cmd
=> 'config' };
2890 __PACKAGE__-
>register_method({
2891 name
=> 'update_snapshot_config',
2892 path
=> '{vmid}/snapshot/{snapname}/config',
2896 description
=> "Update snapshot metadata.",
2898 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2901 additionalProperties
=> 0,
2903 node
=> get_standard_option
('pve-node'),
2904 vmid
=> get_standard_option
('pve-vmid'),
2905 snapname
=> get_standard_option
('pve-snapshot-name'),
2909 description
=> "A textual description or comment.",
2913 returns
=> { type
=> 'null' },
2917 my $rpcenv = PVE
::RPCEnvironment
::get
();
2919 my $authuser = $rpcenv->get_user();
2921 my $vmid = extract_param
($param, 'vmid');
2923 my $snapname = extract_param
($param, 'snapname');
2925 return undef if !defined($param->{description
});
2927 my $updatefn = sub {
2929 my $conf = PVE
::QemuServer
::load_config
($vmid);
2931 PVE
::QemuServer
::check_lock
($conf);
2933 my $snap = $conf->{snapshots
}->{$snapname};
2935 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2937 $snap->{description
} = $param->{description
} if defined($param->{description
});
2939 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2942 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2947 __PACKAGE__-
>register_method({
2948 name
=> 'get_snapshot_config',
2949 path
=> '{vmid}/snapshot/{snapname}/config',
2952 description
=> "Get snapshot configuration",
2954 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2957 additionalProperties
=> 0,
2959 node
=> get_standard_option
('pve-node'),
2960 vmid
=> get_standard_option
('pve-vmid'),
2961 snapname
=> get_standard_option
('pve-snapshot-name'),
2964 returns
=> { type
=> "object" },
2968 my $rpcenv = PVE
::RPCEnvironment
::get
();
2970 my $authuser = $rpcenv->get_user();
2972 my $vmid = extract_param
($param, 'vmid');
2974 my $snapname = extract_param
($param, 'snapname');
2976 my $conf = PVE
::QemuServer
::load_config
($vmid);
2978 my $snap = $conf->{snapshots
}->{$snapname};
2980 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2985 __PACKAGE__-
>register_method({
2987 path
=> '{vmid}/snapshot/{snapname}/rollback',
2991 description
=> "Rollback VM state to specified snapshot.",
2993 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2996 additionalProperties
=> 0,
2998 node
=> get_standard_option
('pve-node'),
2999 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3000 snapname
=> get_standard_option
('pve-snapshot-name'),
3005 description
=> "the task ID.",
3010 my $rpcenv = PVE
::RPCEnvironment
::get
();
3012 my $authuser = $rpcenv->get_user();
3014 my $node = extract_param
($param, 'node');
3016 my $vmid = extract_param
($param, 'vmid');
3018 my $snapname = extract_param
($param, 'snapname');
3021 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3022 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3025 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3028 __PACKAGE__-
>register_method({
3029 name
=> 'delsnapshot',
3030 path
=> '{vmid}/snapshot/{snapname}',
3034 description
=> "Delete a VM snapshot.",
3036 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3039 additionalProperties
=> 0,
3041 node
=> get_standard_option
('pve-node'),
3042 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3043 snapname
=> get_standard_option
('pve-snapshot-name'),
3047 description
=> "For removal from config file, even if removing disk snapshots fails.",
3053 description
=> "the task ID.",
3058 my $rpcenv = PVE
::RPCEnvironment
::get
();
3060 my $authuser = $rpcenv->get_user();
3062 my $node = extract_param
($param, 'node');
3064 my $vmid = extract_param
($param, 'vmid');
3066 my $snapname = extract_param
($param, 'snapname');
3069 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3070 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3073 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3076 __PACKAGE__-
>register_method({
3078 path
=> '{vmid}/template',
3082 description
=> "Create a Template.",
3084 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3085 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3088 additionalProperties
=> 0,
3090 node
=> get_standard_option
('pve-node'),
3091 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3095 description
=> "If you want to convert only 1 disk to base image.",
3096 enum
=> [PVE
::QemuServer
::disknames
()],
3101 returns
=> { type
=> 'null'},
3105 my $rpcenv = PVE
::RPCEnvironment
::get
();
3107 my $authuser = $rpcenv->get_user();
3109 my $node = extract_param
($param, 'node');
3111 my $vmid = extract_param
($param, 'vmid');
3113 my $disk = extract_param
($param, 'disk');
3115 my $updatefn = sub {
3117 my $conf = PVE
::QemuServer
::load_config
($vmid);
3119 PVE
::QemuServer
::check_lock
($conf);
3121 die "unable to create template, because VM contains snapshots\n"
3122 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3124 die "you can't convert a template to a template\n"
3125 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3127 die "you can't convert a VM to template if VM is running\n"
3128 if PVE
::QemuServer
::check_running
($vmid);
3131 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3134 $conf->{template
} = 1;
3135 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3137 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3140 PVE
::QemuServer
::lock_config
($vmid, $updatefn);