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);
1167 # early tests (repeat after locking)
1168 die "VM $vmid is running - destroy failed\n"
1169 if PVE
::QemuServer
::check_running
($vmid);
1174 syslog
('info', "destroy VM $vmid: $upid\n");
1176 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1178 PVE
::AccessControl
::remove_vm_access
($vmid);
1180 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1183 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1186 __PACKAGE__-
>register_method({
1188 path
=> '{vmid}/unlink',
1192 description
=> "Unlink/delete disk images.",
1194 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1197 additionalProperties
=> 0,
1199 node
=> get_standard_option
('pve-node'),
1200 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1202 type
=> 'string', format
=> 'pve-configid-list',
1203 description
=> "A list of disk IDs you want to delete.",
1207 description
=> $opt_force_description,
1212 returns
=> { type
=> 'null'},
1216 $param->{delete} = extract_param
($param, 'idlist');
1218 __PACKAGE__-
>update_vm($param);
1225 __PACKAGE__-
>register_method({
1227 path
=> '{vmid}/vncproxy',
1231 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1233 description
=> "Creates a TCP VNC proxy connections.",
1235 additionalProperties
=> 0,
1237 node
=> get_standard_option
('pve-node'),
1238 vmid
=> get_standard_option
('pve-vmid'),
1242 description
=> "starts websockify instead of vncproxy",
1247 additionalProperties
=> 0,
1249 user
=> { type
=> 'string' },
1250 ticket
=> { type
=> 'string' },
1251 cert
=> { type
=> 'string' },
1252 port
=> { type
=> 'integer' },
1253 upid
=> { type
=> 'string' },
1259 my $rpcenv = PVE
::RPCEnvironment
::get
();
1261 my $authuser = $rpcenv->get_user();
1263 my $vmid = $param->{vmid
};
1264 my $node = $param->{node
};
1265 my $websocket = $param->{websocket
};
1267 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1269 my $authpath = "/vms/$vmid";
1271 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1273 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1276 my ($remip, $family);
1279 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1280 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1281 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1282 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1284 $family = PVE
::Tools
::get_host_address_family
($node);
1287 my $port = PVE
::Tools
::next_vnc_port
($family);
1294 syslog
('info', "starting vnc proxy $upid\n");
1298 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1300 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1302 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1303 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1304 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1305 '-timeout', $timeout, '-authpath', $authpath,
1306 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1309 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1311 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1313 my $qmstr = join(' ', @$qmcmd);
1315 # also redirect stderr (else we get RFB protocol errors)
1316 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1319 PVE
::Tools
::run_command
($cmd);
1324 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1326 PVE
::Tools
::wait_for_vnc_port
($port);
1337 __PACKAGE__-
>register_method({
1338 name
=> 'vncwebsocket',
1339 path
=> '{vmid}/vncwebsocket',
1342 description
=> "You also need to pass a valid ticket (vncticket).",
1343 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1345 description
=> "Opens a weksocket for VNC traffic.",
1347 additionalProperties
=> 0,
1349 node
=> get_standard_option
('pve-node'),
1350 vmid
=> get_standard_option
('pve-vmid'),
1352 description
=> "Ticket from previous call to vncproxy.",
1357 description
=> "Port number returned by previous vncproxy call.",
1367 port
=> { type
=> 'string' },
1373 my $rpcenv = PVE
::RPCEnvironment
::get
();
1375 my $authuser = $rpcenv->get_user();
1377 my $vmid = $param->{vmid
};
1378 my $node = $param->{node
};
1380 my $authpath = "/vms/$vmid";
1382 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1384 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1386 # Note: VNC ports are acessible from outside, so we do not gain any
1387 # security if we verify that $param->{port} belongs to VM $vmid. This
1388 # check is done by verifying the VNC ticket (inside VNC protocol).
1390 my $port = $param->{port
};
1392 return { port
=> $port };
1395 __PACKAGE__-
>register_method({
1396 name
=> 'spiceproxy',
1397 path
=> '{vmid}/spiceproxy',
1402 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1404 description
=> "Returns a SPICE configuration to connect to the VM.",
1406 additionalProperties
=> 0,
1408 node
=> get_standard_option
('pve-node'),
1409 vmid
=> get_standard_option
('pve-vmid'),
1410 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1413 returns
=> get_standard_option
('remote-viewer-config'),
1417 my $rpcenv = PVE
::RPCEnvironment
::get
();
1419 my $authuser = $rpcenv->get_user();
1421 my $vmid = $param->{vmid
};
1422 my $node = $param->{node
};
1423 my $proxy = $param->{proxy
};
1425 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1426 my $title = "VM $vmid";
1427 $title .= " - ". $conf->{name
} if $conf->{name
};
1429 my $port = PVE
::QemuServer
::spice_port
($vmid);
1431 my ($ticket, undef, $remote_viewer_config) =
1432 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1434 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1435 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1437 return $remote_viewer_config;
1440 __PACKAGE__-
>register_method({
1442 path
=> '{vmid}/status',
1445 description
=> "Directory index",
1450 additionalProperties
=> 0,
1452 node
=> get_standard_option
('pve-node'),
1453 vmid
=> get_standard_option
('pve-vmid'),
1461 subdir
=> { type
=> 'string' },
1464 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1470 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1473 { subdir
=> 'current' },
1474 { subdir
=> 'start' },
1475 { subdir
=> 'stop' },
1481 __PACKAGE__-
>register_method({
1482 name
=> 'vm_status',
1483 path
=> '{vmid}/status/current',
1486 protected
=> 1, # qemu pid files are only readable by root
1487 description
=> "Get virtual machine status.",
1489 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1492 additionalProperties
=> 0,
1494 node
=> get_standard_option
('pve-node'),
1495 vmid
=> get_standard_option
('pve-vmid'),
1498 returns
=> { type
=> 'object' },
1503 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1505 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1506 my $status = $vmstatus->{$param->{vmid
}};
1508 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1510 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1515 __PACKAGE__-
>register_method({
1517 path
=> '{vmid}/status/start',
1521 description
=> "Start virtual machine.",
1523 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1526 additionalProperties
=> 0,
1528 node
=> get_standard_option
('pve-node'),
1529 vmid
=> get_standard_option
('pve-vmid'),
1530 skiplock
=> get_standard_option
('skiplock'),
1531 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1532 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1533 machine
=> get_standard_option
('pve-qm-machine'),
1542 my $rpcenv = PVE
::RPCEnvironment
::get
();
1544 my $authuser = $rpcenv->get_user();
1546 my $node = extract_param
($param, 'node');
1548 my $vmid = extract_param
($param, 'vmid');
1550 my $machine = extract_param
($param, 'machine');
1552 my $stateuri = extract_param
($param, 'stateuri');
1553 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1554 if $stateuri && $authuser ne 'root@pam';
1556 my $skiplock = extract_param
($param, 'skiplock');
1557 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1558 if $skiplock && $authuser ne 'root@pam';
1560 my $migratedfrom = extract_param
($param, 'migratedfrom');
1561 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1562 if $migratedfrom && $authuser ne 'root@pam';
1564 # read spice ticket from STDIN
1566 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1567 if (defined(my $line = <>)) {
1569 $spice_ticket = $line;
1573 my $storecfg = PVE
::Storage
::config
();
1575 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1576 $rpcenv->{type
} ne 'ha') {
1581 my $service = "vm:$vmid";
1583 my $cmd = ['ha-manager', 'enable', $service];
1585 print "Executing HA start for VM $vmid\n";
1587 PVE
::Tools
::run_command
($cmd);
1592 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1599 syslog
('info', "start VM $vmid: $upid\n");
1601 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1602 $machine, $spice_ticket);
1607 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1611 __PACKAGE__-
>register_method({
1613 path
=> '{vmid}/status/stop',
1617 description
=> "Stop virtual machine.",
1619 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1622 additionalProperties
=> 0,
1624 node
=> get_standard_option
('pve-node'),
1625 vmid
=> get_standard_option
('pve-vmid'),
1626 skiplock
=> get_standard_option
('skiplock'),
1627 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1629 description
=> "Wait maximal timeout seconds.",
1635 description
=> "Do not decativate storage volumes.",
1648 my $rpcenv = PVE
::RPCEnvironment
::get
();
1650 my $authuser = $rpcenv->get_user();
1652 my $node = extract_param
($param, 'node');
1654 my $vmid = extract_param
($param, 'vmid');
1656 my $skiplock = extract_param
($param, 'skiplock');
1657 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1658 if $skiplock && $authuser ne 'root@pam';
1660 my $keepActive = extract_param
($param, 'keepActive');
1661 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1662 if $keepActive && $authuser ne 'root@pam';
1664 my $migratedfrom = extract_param
($param, 'migratedfrom');
1665 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1666 if $migratedfrom && $authuser ne 'root@pam';
1669 my $storecfg = PVE
::Storage
::config
();
1671 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1676 my $service = "vm:$vmid";
1678 my $cmd = ['ha-manager', 'disable', $service];
1680 print "Executing HA stop for VM $vmid\n";
1682 PVE
::Tools
::run_command
($cmd);
1687 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1693 syslog
('info', "stop VM $vmid: $upid\n");
1695 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1696 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1701 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1705 __PACKAGE__-
>register_method({
1707 path
=> '{vmid}/status/reset',
1711 description
=> "Reset virtual machine.",
1713 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1716 additionalProperties
=> 0,
1718 node
=> get_standard_option
('pve-node'),
1719 vmid
=> get_standard_option
('pve-vmid'),
1720 skiplock
=> get_standard_option
('skiplock'),
1729 my $rpcenv = PVE
::RPCEnvironment
::get
();
1731 my $authuser = $rpcenv->get_user();
1733 my $node = extract_param
($param, 'node');
1735 my $vmid = extract_param
($param, 'vmid');
1737 my $skiplock = extract_param
($param, 'skiplock');
1738 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1739 if $skiplock && $authuser ne 'root@pam';
1741 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1746 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1751 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1754 __PACKAGE__-
>register_method({
1755 name
=> 'vm_shutdown',
1756 path
=> '{vmid}/status/shutdown',
1760 description
=> "Shutdown virtual machine.",
1762 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1765 additionalProperties
=> 0,
1767 node
=> get_standard_option
('pve-node'),
1768 vmid
=> get_standard_option
('pve-vmid'),
1769 skiplock
=> get_standard_option
('skiplock'),
1771 description
=> "Wait maximal timeout seconds.",
1777 description
=> "Make sure the VM stops.",
1783 description
=> "Do not decativate storage volumes.",
1796 my $rpcenv = PVE
::RPCEnvironment
::get
();
1798 my $authuser = $rpcenv->get_user();
1800 my $node = extract_param
($param, 'node');
1802 my $vmid = extract_param
($param, 'vmid');
1804 my $skiplock = extract_param
($param, 'skiplock');
1805 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1806 if $skiplock && $authuser ne 'root@pam';
1808 my $keepActive = extract_param
($param, 'keepActive');
1809 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1810 if $keepActive && $authuser ne 'root@pam';
1812 my $storecfg = PVE
::Storage
::config
();
1817 syslog
('info', "shutdown VM $vmid: $upid\n");
1819 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1820 1, $param->{forceStop
}, $keepActive);
1825 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1828 __PACKAGE__-
>register_method({
1829 name
=> 'vm_suspend',
1830 path
=> '{vmid}/status/suspend',
1834 description
=> "Suspend virtual machine.",
1836 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1839 additionalProperties
=> 0,
1841 node
=> get_standard_option
('pve-node'),
1842 vmid
=> get_standard_option
('pve-vmid'),
1843 skiplock
=> get_standard_option
('skiplock'),
1852 my $rpcenv = PVE
::RPCEnvironment
::get
();
1854 my $authuser = $rpcenv->get_user();
1856 my $node = extract_param
($param, 'node');
1858 my $vmid = extract_param
($param, 'vmid');
1860 my $skiplock = extract_param
($param, 'skiplock');
1861 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1862 if $skiplock && $authuser ne 'root@pam';
1864 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1869 syslog
('info', "suspend VM $vmid: $upid\n");
1871 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1876 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1879 __PACKAGE__-
>register_method({
1880 name
=> 'vm_resume',
1881 path
=> '{vmid}/status/resume',
1885 description
=> "Resume virtual machine.",
1887 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1890 additionalProperties
=> 0,
1892 node
=> get_standard_option
('pve-node'),
1893 vmid
=> get_standard_option
('pve-vmid'),
1894 skiplock
=> get_standard_option
('skiplock'),
1895 nocheck
=> { type
=> 'boolean', optional
=> 1 },
1905 my $rpcenv = PVE
::RPCEnvironment
::get
();
1907 my $authuser = $rpcenv->get_user();
1909 my $node = extract_param
($param, 'node');
1911 my $vmid = extract_param
($param, 'vmid');
1913 my $skiplock = extract_param
($param, 'skiplock');
1914 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1915 if $skiplock && $authuser ne 'root@pam';
1917 my $nocheck = extract_param
($param, 'nocheck');
1919 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
1924 syslog
('info', "resume VM $vmid: $upid\n");
1926 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
1931 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1934 __PACKAGE__-
>register_method({
1935 name
=> 'vm_sendkey',
1936 path
=> '{vmid}/sendkey',
1940 description
=> "Send key event to virtual machine.",
1942 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1945 additionalProperties
=> 0,
1947 node
=> get_standard_option
('pve-node'),
1948 vmid
=> get_standard_option
('pve-vmid'),
1949 skiplock
=> get_standard_option
('skiplock'),
1951 description
=> "The key (qemu monitor encoding).",
1956 returns
=> { type
=> 'null'},
1960 my $rpcenv = PVE
::RPCEnvironment
::get
();
1962 my $authuser = $rpcenv->get_user();
1964 my $node = extract_param
($param, 'node');
1966 my $vmid = extract_param
($param, 'vmid');
1968 my $skiplock = extract_param
($param, 'skiplock');
1969 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1970 if $skiplock && $authuser ne 'root@pam';
1972 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1977 __PACKAGE__-
>register_method({
1978 name
=> 'vm_feature',
1979 path
=> '{vmid}/feature',
1983 description
=> "Check if feature for virtual machine is available.",
1985 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1988 additionalProperties
=> 0,
1990 node
=> get_standard_option
('pve-node'),
1991 vmid
=> get_standard_option
('pve-vmid'),
1993 description
=> "Feature to check.",
1995 enum
=> [ 'snapshot', 'clone', 'copy' ],
1997 snapname
=> get_standard_option
('pve-snapshot-name', {
2005 hasFeature
=> { type
=> 'boolean' },
2008 items
=> { type
=> 'string' },
2015 my $node = extract_param
($param, 'node');
2017 my $vmid = extract_param
($param, 'vmid');
2019 my $snapname = extract_param
($param, 'snapname');
2021 my $feature = extract_param
($param, 'feature');
2023 my $running = PVE
::QemuServer
::check_running
($vmid);
2025 my $conf = PVE
::QemuServer
::load_config
($vmid);
2028 my $snap = $conf->{snapshots
}->{$snapname};
2029 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2032 my $storecfg = PVE
::Storage
::config
();
2034 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2035 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2038 hasFeature
=> $hasFeature,
2039 nodes
=> [ keys %$nodelist ],
2043 __PACKAGE__-
>register_method({
2045 path
=> '{vmid}/clone',
2049 description
=> "Create a copy of virtual machine/template.",
2051 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2052 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2053 "'Datastore.AllocateSpace' on any used storage.",
2056 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2058 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2059 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2064 additionalProperties
=> 0,
2066 node
=> get_standard_option
('pve-node'),
2067 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2068 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2071 type
=> 'string', format
=> 'dns-name',
2072 description
=> "Set a name for the new VM.",
2077 description
=> "Description for the new VM.",
2081 type
=> 'string', format
=> 'pve-poolid',
2082 description
=> "Add the new VM to the specified pool.",
2084 snapname
=> get_standard_option
('pve-snapshot-name', {
2087 storage
=> get_standard_option
('pve-storage-id', {
2088 description
=> "Target storage for full clone.",
2093 description
=> "Target format for file storage.",
2097 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2102 description
=> "Create a full copy of all disk. This is always done when " .
2103 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2106 target
=> get_standard_option
('pve-node', {
2107 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2118 my $rpcenv = PVE
::RPCEnvironment
::get
();
2120 my $authuser = $rpcenv->get_user();
2122 my $node = extract_param
($param, 'node');
2124 my $vmid = extract_param
($param, 'vmid');
2126 my $newid = extract_param
($param, 'newid');
2128 my $pool = extract_param
($param, 'pool');
2130 if (defined($pool)) {
2131 $rpcenv->check_pool_exist($pool);
2134 my $snapname = extract_param
($param, 'snapname');
2136 my $storage = extract_param
($param, 'storage');
2138 my $format = extract_param
($param, 'format');
2140 my $target = extract_param
($param, 'target');
2142 my $localnode = PVE
::INotify
::nodename
();
2144 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2146 PVE
::Cluster
::check_node_exists
($target) if $target;
2148 my $storecfg = PVE
::Storage
::config
();
2151 # check if storage is enabled on local node
2152 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2154 # check if storage is available on target node
2155 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2156 # clone only works if target storage is shared
2157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2158 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2162 PVE
::Cluster
::check_cfs_quorum
();
2164 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2166 # exclusive lock if VM is running - else shared lock is enough;
2167 my $shared_lock = $running ?
0 : 1;
2171 # do all tests after lock
2172 # we also try to do all tests before we fork the worker
2174 my $conf = PVE
::QemuServer
::load_config
($vmid);
2176 PVE
::QemuServer
::check_lock
($conf);
2178 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2180 die "unexpected state change\n" if $verify_running != $running;
2182 die "snapshot '$snapname' does not exist\n"
2183 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2185 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2187 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2189 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2191 my $conffile = PVE
::QemuServer
::config_file
($newid);
2193 die "unable to create VM $newid: config file already exists\n"
2196 my $newconf = { lock => 'clone' };
2201 foreach my $opt (keys %$oldconf) {
2202 my $value = $oldconf->{$opt};
2204 # do not copy snapshot related info
2205 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2206 $opt eq 'vmstate' || $opt eq 'snapstate';
2208 # no need to copy unused images, because VMID(owner) changes anyways
2209 next if $opt =~ m/^unused\d+$/;
2211 # always change MAC! address
2212 if ($opt =~ m/^net(\d+)$/) {
2213 my $net = PVE
::QemuServer
::parse_net
($value);
2214 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2215 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2216 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2217 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2218 die "unable to parse drive options for '$opt'\n" if !$drive;
2219 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2220 $newconf->{$opt} = $value; # simply copy configuration
2222 if ($param->{full
}) {
2223 die "Full clone feature is not available"
2224 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2225 $fullclone->{$opt} = 1;
2227 # not full means clone instead of copy
2228 die "Linked clone feature is not available"
2229 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2231 $drives->{$opt} = $drive;
2232 push @$vollist, $drive->{file
};
2235 # copy everything else
2236 $newconf->{$opt} = $value;
2240 # auto generate a new uuid
2241 my ($uuid, $uuid_str);
2242 UUID
::generate
($uuid);
2243 UUID
::unparse
($uuid, $uuid_str);
2244 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2245 $smbios1->{uuid
} = $uuid_str;
2246 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2248 delete $newconf->{template
};
2250 if ($param->{name
}) {
2251 $newconf->{name
} = $param->{name
};
2253 if ($oldconf->{name
}) {
2254 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2256 $newconf->{name
} = "Copy-of-VM-$vmid";
2260 if ($param->{description
}) {
2261 $newconf->{description
} = $param->{description
};
2264 # create empty/temp config - this fails if VM already exists on other node
2265 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2270 my $newvollist = [];
2273 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2275 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2277 foreach my $opt (keys %$drives) {
2278 my $drive = $drives->{$opt};
2280 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2281 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2283 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2285 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2288 delete $newconf->{lock};
2289 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2292 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2293 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2295 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2296 die "Failed to move config to node '$target' - rename failed: $!\n"
2297 if !rename($conffile, $newconffile);
2300 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2305 sleep 1; # some storage like rbd need to wait before release volume - really?
2307 foreach my $volid (@$newvollist) {
2308 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2311 die "clone failed: $err";
2317 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2319 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2322 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2323 # Aquire exclusive lock lock for $newid
2324 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2329 __PACKAGE__-
>register_method({
2330 name
=> 'move_vm_disk',
2331 path
=> '{vmid}/move_disk',
2335 description
=> "Move volume to different storage.",
2337 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2338 "and 'Datastore.AllocateSpace' permissions on the storage.",
2341 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2342 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2346 additionalProperties
=> 0,
2348 node
=> get_standard_option
('pve-node'),
2349 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2352 description
=> "The disk you want to move.",
2353 enum
=> [ PVE
::QemuServer
::disknames
() ],
2355 storage
=> get_standard_option
('pve-storage-id', {
2356 description
=> "Target storage.",
2357 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2361 description
=> "Target Format.",
2362 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2367 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2373 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2381 description
=> "the task ID.",
2386 my $rpcenv = PVE
::RPCEnvironment
::get
();
2388 my $authuser = $rpcenv->get_user();
2390 my $node = extract_param
($param, 'node');
2392 my $vmid = extract_param
($param, 'vmid');
2394 my $digest = extract_param
($param, 'digest');
2396 my $disk = extract_param
($param, 'disk');
2398 my $storeid = extract_param
($param, 'storage');
2400 my $format = extract_param
($param, 'format');
2402 my $storecfg = PVE
::Storage
::config
();
2404 my $updatefn = sub {
2406 my $conf = PVE
::QemuServer
::load_config
($vmid);
2408 die "checksum missmatch (file change by other user?)\n"
2409 if $digest && $digest ne $conf->{digest
};
2411 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2413 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2415 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2417 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2420 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2421 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2425 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2426 (!$format || !$oldfmt || $oldfmt eq $format);
2428 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2430 my $running = PVE
::QemuServer
::check_running
($vmid);
2432 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2436 my $newvollist = [];
2439 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2441 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2442 $vmid, $storeid, $format, 1, $newvollist);
2444 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2446 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2448 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2451 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2452 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2459 foreach my $volid (@$newvollist) {
2460 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2463 die "storage migration failed: $err";
2466 if ($param->{delete}) {
2467 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2468 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2469 if ($used_paths->{$path}){
2470 warn "volume $old_volid have snapshots. Can't delete it\n";
2471 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2472 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2474 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2480 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2483 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2486 __PACKAGE__-
>register_method({
2487 name
=> 'migrate_vm',
2488 path
=> '{vmid}/migrate',
2492 description
=> "Migrate virtual machine. Creates a new migration task.",
2494 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2497 additionalProperties
=> 0,
2499 node
=> get_standard_option
('pve-node'),
2500 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2501 target
=> get_standard_option
('pve-node', {
2502 description
=> "Target node.",
2503 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2507 description
=> "Use online/live migration.",
2512 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2519 description
=> "the task ID.",
2524 my $rpcenv = PVE
::RPCEnvironment
::get
();
2526 my $authuser = $rpcenv->get_user();
2528 my $target = extract_param
($param, 'target');
2530 my $localnode = PVE
::INotify
::nodename
();
2531 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2533 PVE
::Cluster
::check_cfs_quorum
();
2535 PVE
::Cluster
::check_node_exists
($target);
2537 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2539 my $vmid = extract_param
($param, 'vmid');
2541 raise_param_exc
({ force
=> "Only root may use this option." })
2542 if $param->{force
} && $authuser ne 'root@pam';
2545 my $conf = PVE
::QemuServer
::load_config
($vmid);
2547 # try to detect errors early
2549 PVE
::QemuServer
::check_lock
($conf);
2551 if (PVE
::QemuServer
::check_running
($vmid)) {
2552 die "cant migrate running VM without --online\n"
2553 if !$param->{online
};
2556 my $storecfg = PVE
::Storage
::config
();
2557 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2559 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2564 my $service = "vm:$vmid";
2566 my $cmd = ['ha-manager', 'migrate', $service, $target];
2568 print "Executing HA migrate for VM $vmid to node $target\n";
2570 PVE
::Tools
::run_command
($cmd);
2575 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2582 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2585 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2590 __PACKAGE__-
>register_method({
2592 path
=> '{vmid}/monitor',
2596 description
=> "Execute Qemu monitor commands.",
2598 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2601 additionalProperties
=> 0,
2603 node
=> get_standard_option
('pve-node'),
2604 vmid
=> get_standard_option
('pve-vmid'),
2607 description
=> "The monitor command.",
2611 returns
=> { type
=> 'string'},
2615 my $vmid = $param->{vmid
};
2617 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2621 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2623 $res = "ERROR: $@" if $@;
2628 __PACKAGE__-
>register_method({
2629 name
=> 'resize_vm',
2630 path
=> '{vmid}/resize',
2634 description
=> "Extend volume size.",
2636 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2639 additionalProperties
=> 0,
2641 node
=> get_standard_option
('pve-node'),
2642 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2643 skiplock
=> get_standard_option
('skiplock'),
2646 description
=> "The disk you want to resize.",
2647 enum
=> [PVE
::QemuServer
::disknames
()],
2651 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2652 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.",
2656 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2662 returns
=> { type
=> 'null'},
2666 my $rpcenv = PVE
::RPCEnvironment
::get
();
2668 my $authuser = $rpcenv->get_user();
2670 my $node = extract_param
($param, 'node');
2672 my $vmid = extract_param
($param, 'vmid');
2674 my $digest = extract_param
($param, 'digest');
2676 my $disk = extract_param
($param, 'disk');
2678 my $sizestr = extract_param
($param, 'size');
2680 my $skiplock = extract_param
($param, 'skiplock');
2681 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2682 if $skiplock && $authuser ne 'root@pam';
2684 my $storecfg = PVE
::Storage
::config
();
2686 my $updatefn = sub {
2688 my $conf = PVE
::QemuServer
::load_config
($vmid);
2690 die "checksum missmatch (file change by other user?)\n"
2691 if $digest && $digest ne $conf->{digest
};
2692 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2694 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2696 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2698 my (undef, undef, undef, undef, undef, undef, $format) =
2699 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2701 die "can't resize volume: $disk if snapshot exists\n"
2702 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2704 my $volid = $drive->{file
};
2706 die "disk '$disk' has no associated volume\n" if !$volid;
2708 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2710 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2712 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2714 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2716 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2717 my ($ext, $newsize, $unit) = ($1, $2, $4);
2720 $newsize = $newsize * 1024;
2721 } elsif ($unit eq 'M') {
2722 $newsize = $newsize * 1024 * 1024;
2723 } elsif ($unit eq 'G') {
2724 $newsize = $newsize * 1024 * 1024 * 1024;
2725 } elsif ($unit eq 'T') {
2726 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2729 $newsize += $size if $ext;
2730 $newsize = int($newsize);
2732 die "unable to skrink disk size\n" if $newsize < $size;
2734 return if $size == $newsize;
2736 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2738 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2740 $drive->{size
} = $newsize;
2741 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2743 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2746 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2750 __PACKAGE__-
>register_method({
2751 name
=> 'snapshot_list',
2752 path
=> '{vmid}/snapshot',
2754 description
=> "List all snapshots.",
2756 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2759 protected
=> 1, # qemu pid files are only readable by root
2761 additionalProperties
=> 0,
2763 vmid
=> get_standard_option
('pve-vmid'),
2764 node
=> get_standard_option
('pve-node'),
2773 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2778 my $vmid = $param->{vmid
};
2780 my $conf = PVE
::QemuServer
::load_config
($vmid);
2781 my $snaphash = $conf->{snapshots
} || {};
2785 foreach my $name (keys %$snaphash) {
2786 my $d = $snaphash->{$name};
2789 snaptime
=> $d->{snaptime
} || 0,
2790 vmstate
=> $d->{vmstate
} ?
1 : 0,
2791 description
=> $d->{description
} || '',
2793 $item->{parent
} = $d->{parent
} if $d->{parent
};
2794 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2798 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2799 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2800 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2802 push @$res, $current;
2807 __PACKAGE__-
>register_method({
2809 path
=> '{vmid}/snapshot',
2813 description
=> "Snapshot a VM.",
2815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2818 additionalProperties
=> 0,
2820 node
=> get_standard_option
('pve-node'),
2821 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2822 snapname
=> get_standard_option
('pve-snapshot-name'),
2826 description
=> "Save the vmstate",
2831 description
=> "A textual description or comment.",
2837 description
=> "the task ID.",
2842 my $rpcenv = PVE
::RPCEnvironment
::get
();
2844 my $authuser = $rpcenv->get_user();
2846 my $node = extract_param
($param, 'node');
2848 my $vmid = extract_param
($param, 'vmid');
2850 my $snapname = extract_param
($param, 'snapname');
2852 die "unable to use snapshot name 'current' (reserved name)\n"
2853 if $snapname eq 'current';
2856 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2857 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2858 $param->{description
});
2861 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2864 __PACKAGE__-
>register_method({
2865 name
=> 'snapshot_cmd_idx',
2866 path
=> '{vmid}/snapshot/{snapname}',
2873 additionalProperties
=> 0,
2875 vmid
=> get_standard_option
('pve-vmid'),
2876 node
=> get_standard_option
('pve-node'),
2877 snapname
=> get_standard_option
('pve-snapshot-name'),
2886 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2893 push @$res, { cmd
=> 'rollback' };
2894 push @$res, { cmd
=> 'config' };
2899 __PACKAGE__-
>register_method({
2900 name
=> 'update_snapshot_config',
2901 path
=> '{vmid}/snapshot/{snapname}/config',
2905 description
=> "Update snapshot metadata.",
2907 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2910 additionalProperties
=> 0,
2912 node
=> get_standard_option
('pve-node'),
2913 vmid
=> get_standard_option
('pve-vmid'),
2914 snapname
=> get_standard_option
('pve-snapshot-name'),
2918 description
=> "A textual description or comment.",
2922 returns
=> { type
=> 'null' },
2926 my $rpcenv = PVE
::RPCEnvironment
::get
();
2928 my $authuser = $rpcenv->get_user();
2930 my $vmid = extract_param
($param, 'vmid');
2932 my $snapname = extract_param
($param, 'snapname');
2934 return undef if !defined($param->{description
});
2936 my $updatefn = sub {
2938 my $conf = PVE
::QemuServer
::load_config
($vmid);
2940 PVE
::QemuServer
::check_lock
($conf);
2942 my $snap = $conf->{snapshots
}->{$snapname};
2944 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2946 $snap->{description
} = $param->{description
} if defined($param->{description
});
2948 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2951 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2956 __PACKAGE__-
>register_method({
2957 name
=> 'get_snapshot_config',
2958 path
=> '{vmid}/snapshot/{snapname}/config',
2961 description
=> "Get snapshot configuration",
2963 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2966 additionalProperties
=> 0,
2968 node
=> get_standard_option
('pve-node'),
2969 vmid
=> get_standard_option
('pve-vmid'),
2970 snapname
=> get_standard_option
('pve-snapshot-name'),
2973 returns
=> { type
=> "object" },
2977 my $rpcenv = PVE
::RPCEnvironment
::get
();
2979 my $authuser = $rpcenv->get_user();
2981 my $vmid = extract_param
($param, 'vmid');
2983 my $snapname = extract_param
($param, 'snapname');
2985 my $conf = PVE
::QemuServer
::load_config
($vmid);
2987 my $snap = $conf->{snapshots
}->{$snapname};
2989 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2994 __PACKAGE__-
>register_method({
2996 path
=> '{vmid}/snapshot/{snapname}/rollback',
3000 description
=> "Rollback VM state to specified snapshot.",
3002 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3005 additionalProperties
=> 0,
3007 node
=> get_standard_option
('pve-node'),
3008 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3009 snapname
=> get_standard_option
('pve-snapshot-name'),
3014 description
=> "the task ID.",
3019 my $rpcenv = PVE
::RPCEnvironment
::get
();
3021 my $authuser = $rpcenv->get_user();
3023 my $node = extract_param
($param, 'node');
3025 my $vmid = extract_param
($param, 'vmid');
3027 my $snapname = extract_param
($param, 'snapname');
3030 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3031 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3034 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3037 __PACKAGE__-
>register_method({
3038 name
=> 'delsnapshot',
3039 path
=> '{vmid}/snapshot/{snapname}',
3043 description
=> "Delete a VM snapshot.",
3045 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3048 additionalProperties
=> 0,
3050 node
=> get_standard_option
('pve-node'),
3051 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3052 snapname
=> get_standard_option
('pve-snapshot-name'),
3056 description
=> "For removal from config file, even if removing disk snapshots fails.",
3062 description
=> "the task ID.",
3067 my $rpcenv = PVE
::RPCEnvironment
::get
();
3069 my $authuser = $rpcenv->get_user();
3071 my $node = extract_param
($param, 'node');
3073 my $vmid = extract_param
($param, 'vmid');
3075 my $snapname = extract_param
($param, 'snapname');
3078 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3079 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3082 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3085 __PACKAGE__-
>register_method({
3087 path
=> '{vmid}/template',
3091 description
=> "Create a Template.",
3093 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3094 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3097 additionalProperties
=> 0,
3099 node
=> get_standard_option
('pve-node'),
3100 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3104 description
=> "If you want to convert only 1 disk to base image.",
3105 enum
=> [PVE
::QemuServer
::disknames
()],
3110 returns
=> { type
=> 'null'},
3114 my $rpcenv = PVE
::RPCEnvironment
::get
();
3116 my $authuser = $rpcenv->get_user();
3118 my $node = extract_param
($param, 'node');
3120 my $vmid = extract_param
($param, 'vmid');
3122 my $disk = extract_param
($param, 'disk');
3124 my $updatefn = sub {
3126 my $conf = PVE
::QemuServer
::load_config
($vmid);
3128 PVE
::QemuServer
::check_lock
($conf);
3130 die "unable to create template, because VM contains snapshots\n"
3131 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3133 die "you can't convert a template to a template\n"
3134 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3136 die "you can't convert a VM to template if VM is running\n"
3137 if PVE
::QemuServer
::check_running
($vmid);
3140 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3143 $conf->{template
} = 1;
3144 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3146 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3149 PVE
::QemuServer
::lock_config
($vmid, $updatefn);