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 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1531 skiplock
=> get_standard_option
('skiplock'),
1532 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1533 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1534 machine
=> get_standard_option
('pve-qm-machine'),
1543 my $rpcenv = PVE
::RPCEnvironment
::get
();
1545 my $authuser = $rpcenv->get_user();
1547 my $node = extract_param
($param, 'node');
1549 my $vmid = extract_param
($param, 'vmid');
1551 my $machine = extract_param
($param, 'machine');
1553 my $stateuri = extract_param
($param, 'stateuri');
1554 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1555 if $stateuri && $authuser ne 'root@pam';
1557 my $skiplock = extract_param
($param, 'skiplock');
1558 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1559 if $skiplock && $authuser ne 'root@pam';
1561 my $migratedfrom = extract_param
($param, 'migratedfrom');
1562 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1563 if $migratedfrom && $authuser ne 'root@pam';
1565 # read spice ticket from STDIN
1567 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1568 if (defined(my $line = <>)) {
1570 $spice_ticket = $line;
1574 my $storecfg = PVE
::Storage
::config
();
1576 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1577 $rpcenv->{type
} ne 'ha') {
1582 my $service = "vm:$vmid";
1584 my $cmd = ['ha-manager', 'enable', $service];
1586 print "Executing HA start for VM $vmid\n";
1588 PVE
::Tools
::run_command
($cmd);
1593 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1600 syslog
('info', "start VM $vmid: $upid\n");
1602 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1603 $machine, $spice_ticket);
1608 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1612 __PACKAGE__-
>register_method({
1614 path
=> '{vmid}/status/stop',
1618 description
=> "Stop virtual machine.",
1620 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1623 additionalProperties
=> 0,
1625 node
=> get_standard_option
('pve-node'),
1626 vmid
=> get_standard_option
('pve-vmid',
1627 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1628 skiplock
=> get_standard_option
('skiplock'),
1629 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1631 description
=> "Wait maximal timeout seconds.",
1637 description
=> "Do not decativate storage volumes.",
1650 my $rpcenv = PVE
::RPCEnvironment
::get
();
1652 my $authuser = $rpcenv->get_user();
1654 my $node = extract_param
($param, 'node');
1656 my $vmid = extract_param
($param, 'vmid');
1658 my $skiplock = extract_param
($param, 'skiplock');
1659 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1660 if $skiplock && $authuser ne 'root@pam';
1662 my $keepActive = extract_param
($param, 'keepActive');
1663 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1664 if $keepActive && $authuser ne 'root@pam';
1666 my $migratedfrom = extract_param
($param, 'migratedfrom');
1667 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1668 if $migratedfrom && $authuser ne 'root@pam';
1671 my $storecfg = PVE
::Storage
::config
();
1673 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1678 my $service = "vm:$vmid";
1680 my $cmd = ['ha-manager', 'disable', $service];
1682 print "Executing HA stop for VM $vmid\n";
1684 PVE
::Tools
::run_command
($cmd);
1689 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1695 syslog
('info', "stop VM $vmid: $upid\n");
1697 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1698 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1703 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1707 __PACKAGE__-
>register_method({
1709 path
=> '{vmid}/status/reset',
1713 description
=> "Reset virtual machine.",
1715 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1718 additionalProperties
=> 0,
1720 node
=> get_standard_option
('pve-node'),
1721 vmid
=> get_standard_option
('pve-vmid',
1722 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1723 skiplock
=> get_standard_option
('skiplock'),
1732 my $rpcenv = PVE
::RPCEnvironment
::get
();
1734 my $authuser = $rpcenv->get_user();
1736 my $node = extract_param
($param, 'node');
1738 my $vmid = extract_param
($param, 'vmid');
1740 my $skiplock = extract_param
($param, 'skiplock');
1741 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1742 if $skiplock && $authuser ne 'root@pam';
1744 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1749 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1754 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1757 __PACKAGE__-
>register_method({
1758 name
=> 'vm_shutdown',
1759 path
=> '{vmid}/status/shutdown',
1763 description
=> "Shutdown virtual machine.",
1765 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1768 additionalProperties
=> 0,
1770 node
=> get_standard_option
('pve-node'),
1771 vmid
=> get_standard_option
('pve-vmid',
1772 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1773 skiplock
=> get_standard_option
('skiplock'),
1775 description
=> "Wait maximal timeout seconds.",
1781 description
=> "Make sure the VM stops.",
1787 description
=> "Do not decativate storage volumes.",
1800 my $rpcenv = PVE
::RPCEnvironment
::get
();
1802 my $authuser = $rpcenv->get_user();
1804 my $node = extract_param
($param, 'node');
1806 my $vmid = extract_param
($param, 'vmid');
1808 my $skiplock = extract_param
($param, 'skiplock');
1809 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1810 if $skiplock && $authuser ne 'root@pam';
1812 my $keepActive = extract_param
($param, 'keepActive');
1813 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1814 if $keepActive && $authuser ne 'root@pam';
1816 my $storecfg = PVE
::Storage
::config
();
1821 syslog
('info', "shutdown VM $vmid: $upid\n");
1823 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1824 1, $param->{forceStop
}, $keepActive);
1829 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1832 __PACKAGE__-
>register_method({
1833 name
=> 'vm_suspend',
1834 path
=> '{vmid}/status/suspend',
1838 description
=> "Suspend virtual machine.",
1840 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1843 additionalProperties
=> 0,
1845 node
=> get_standard_option
('pve-node'),
1846 vmid
=> get_standard_option
('pve-vmid',
1847 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1848 skiplock
=> get_standard_option
('skiplock'),
1857 my $rpcenv = PVE
::RPCEnvironment
::get
();
1859 my $authuser = $rpcenv->get_user();
1861 my $node = extract_param
($param, 'node');
1863 my $vmid = extract_param
($param, 'vmid');
1865 my $skiplock = extract_param
($param, 'skiplock');
1866 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1867 if $skiplock && $authuser ne 'root@pam';
1869 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1874 syslog
('info', "suspend VM $vmid: $upid\n");
1876 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1881 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1884 __PACKAGE__-
>register_method({
1885 name
=> 'vm_resume',
1886 path
=> '{vmid}/status/resume',
1890 description
=> "Resume virtual machine.",
1892 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1895 additionalProperties
=> 0,
1897 node
=> get_standard_option
('pve-node'),
1898 vmid
=> get_standard_option
('pve-vmid',
1899 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1900 skiplock
=> get_standard_option
('skiplock'),
1901 nocheck
=> { type
=> 'boolean', optional
=> 1 },
1911 my $rpcenv = PVE
::RPCEnvironment
::get
();
1913 my $authuser = $rpcenv->get_user();
1915 my $node = extract_param
($param, 'node');
1917 my $vmid = extract_param
($param, 'vmid');
1919 my $skiplock = extract_param
($param, 'skiplock');
1920 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1921 if $skiplock && $authuser ne 'root@pam';
1923 my $nocheck = extract_param
($param, 'nocheck');
1925 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
1930 syslog
('info', "resume VM $vmid: $upid\n");
1932 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
1937 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1940 __PACKAGE__-
>register_method({
1941 name
=> 'vm_sendkey',
1942 path
=> '{vmid}/sendkey',
1946 description
=> "Send key event to virtual machine.",
1948 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1951 additionalProperties
=> 0,
1953 node
=> get_standard_option
('pve-node'),
1954 vmid
=> get_standard_option
('pve-vmid',
1955 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1956 skiplock
=> get_standard_option
('skiplock'),
1958 description
=> "The key (qemu monitor encoding).",
1963 returns
=> { type
=> 'null'},
1967 my $rpcenv = PVE
::RPCEnvironment
::get
();
1969 my $authuser = $rpcenv->get_user();
1971 my $node = extract_param
($param, 'node');
1973 my $vmid = extract_param
($param, 'vmid');
1975 my $skiplock = extract_param
($param, 'skiplock');
1976 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1977 if $skiplock && $authuser ne 'root@pam';
1979 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1984 __PACKAGE__-
>register_method({
1985 name
=> 'vm_feature',
1986 path
=> '{vmid}/feature',
1990 description
=> "Check if feature for virtual machine is available.",
1992 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1995 additionalProperties
=> 0,
1997 node
=> get_standard_option
('pve-node'),
1998 vmid
=> get_standard_option
('pve-vmid'),
2000 description
=> "Feature to check.",
2002 enum
=> [ 'snapshot', 'clone', 'copy' ],
2004 snapname
=> get_standard_option
('pve-snapshot-name', {
2012 hasFeature
=> { type
=> 'boolean' },
2015 items
=> { type
=> 'string' },
2022 my $node = extract_param
($param, 'node');
2024 my $vmid = extract_param
($param, 'vmid');
2026 my $snapname = extract_param
($param, 'snapname');
2028 my $feature = extract_param
($param, 'feature');
2030 my $running = PVE
::QemuServer
::check_running
($vmid);
2032 my $conf = PVE
::QemuServer
::load_config
($vmid);
2035 my $snap = $conf->{snapshots
}->{$snapname};
2036 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2039 my $storecfg = PVE
::Storage
::config
();
2041 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2042 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2045 hasFeature
=> $hasFeature,
2046 nodes
=> [ keys %$nodelist ],
2050 __PACKAGE__-
>register_method({
2052 path
=> '{vmid}/clone',
2056 description
=> "Create a copy of virtual machine/template.",
2058 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2059 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2060 "'Datastore.AllocateSpace' on any used storage.",
2063 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2065 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2066 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2071 additionalProperties
=> 0,
2073 node
=> get_standard_option
('pve-node'),
2074 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2075 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2078 type
=> 'string', format
=> 'dns-name',
2079 description
=> "Set a name for the new VM.",
2084 description
=> "Description for the new VM.",
2088 type
=> 'string', format
=> 'pve-poolid',
2089 description
=> "Add the new VM to the specified pool.",
2091 snapname
=> get_standard_option
('pve-snapshot-name', {
2094 storage
=> get_standard_option
('pve-storage-id', {
2095 description
=> "Target storage for full clone.",
2100 description
=> "Target format for file storage.",
2104 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2109 description
=> "Create a full copy of all disk. This is always done when " .
2110 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2113 target
=> get_standard_option
('pve-node', {
2114 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2125 my $rpcenv = PVE
::RPCEnvironment
::get
();
2127 my $authuser = $rpcenv->get_user();
2129 my $node = extract_param
($param, 'node');
2131 my $vmid = extract_param
($param, 'vmid');
2133 my $newid = extract_param
($param, 'newid');
2135 my $pool = extract_param
($param, 'pool');
2137 if (defined($pool)) {
2138 $rpcenv->check_pool_exist($pool);
2141 my $snapname = extract_param
($param, 'snapname');
2143 my $storage = extract_param
($param, 'storage');
2145 my $format = extract_param
($param, 'format');
2147 my $target = extract_param
($param, 'target');
2149 my $localnode = PVE
::INotify
::nodename
();
2151 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2153 PVE
::Cluster
::check_node_exists
($target) if $target;
2155 my $storecfg = PVE
::Storage
::config
();
2158 # check if storage is enabled on local node
2159 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2161 # check if storage is available on target node
2162 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2163 # clone only works if target storage is shared
2164 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2165 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2169 PVE
::Cluster
::check_cfs_quorum
();
2171 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2173 # exclusive lock if VM is running - else shared lock is enough;
2174 my $shared_lock = $running ?
0 : 1;
2178 # do all tests after lock
2179 # we also try to do all tests before we fork the worker
2181 my $conf = PVE
::QemuServer
::load_config
($vmid);
2183 PVE
::QemuServer
::check_lock
($conf);
2185 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2187 die "unexpected state change\n" if $verify_running != $running;
2189 die "snapshot '$snapname' does not exist\n"
2190 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2192 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2194 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2196 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2198 my $conffile = PVE
::QemuServer
::config_file
($newid);
2200 die "unable to create VM $newid: config file already exists\n"
2203 my $newconf = { lock => 'clone' };
2208 foreach my $opt (keys %$oldconf) {
2209 my $value = $oldconf->{$opt};
2211 # do not copy snapshot related info
2212 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2213 $opt eq 'vmstate' || $opt eq 'snapstate';
2215 # no need to copy unused images, because VMID(owner) changes anyways
2216 next if $opt =~ m/^unused\d+$/;
2218 # always change MAC! address
2219 if ($opt =~ m/^net(\d+)$/) {
2220 my $net = PVE
::QemuServer
::parse_net
($value);
2221 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2222 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2223 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2224 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2225 die "unable to parse drive options for '$opt'\n" if !$drive;
2226 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2227 $newconf->{$opt} = $value; # simply copy configuration
2229 if ($param->{full
}) {
2230 die "Full clone feature is not available"
2231 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2232 $fullclone->{$opt} = 1;
2234 # not full means clone instead of copy
2235 die "Linked clone feature is not available"
2236 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2238 $drives->{$opt} = $drive;
2239 push @$vollist, $drive->{file
};
2242 # copy everything else
2243 $newconf->{$opt} = $value;
2247 # auto generate a new uuid
2248 my ($uuid, $uuid_str);
2249 UUID
::generate
($uuid);
2250 UUID
::unparse
($uuid, $uuid_str);
2251 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2252 $smbios1->{uuid
} = $uuid_str;
2253 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2255 delete $newconf->{template
};
2257 if ($param->{name
}) {
2258 $newconf->{name
} = $param->{name
};
2260 if ($oldconf->{name
}) {
2261 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2263 $newconf->{name
} = "Copy-of-VM-$vmid";
2267 if ($param->{description
}) {
2268 $newconf->{description
} = $param->{description
};
2271 # create empty/temp config - this fails if VM already exists on other node
2272 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2277 my $newvollist = [];
2280 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2282 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2284 foreach my $opt (keys %$drives) {
2285 my $drive = $drives->{$opt};
2287 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2288 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2290 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2292 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2295 delete $newconf->{lock};
2296 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2299 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2300 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2302 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2303 die "Failed to move config to node '$target' - rename failed: $!\n"
2304 if !rename($conffile, $newconffile);
2307 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2312 sleep 1; # some storage like rbd need to wait before release volume - really?
2314 foreach my $volid (@$newvollist) {
2315 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2318 die "clone failed: $err";
2324 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2326 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2329 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2330 # Aquire exclusive lock lock for $newid
2331 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2336 __PACKAGE__-
>register_method({
2337 name
=> 'move_vm_disk',
2338 path
=> '{vmid}/move_disk',
2342 description
=> "Move volume to different storage.",
2344 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2345 "and 'Datastore.AllocateSpace' permissions on the storage.",
2348 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2349 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2353 additionalProperties
=> 0,
2355 node
=> get_standard_option
('pve-node'),
2356 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2359 description
=> "The disk you want to move.",
2360 enum
=> [ PVE
::QemuServer
::disknames
() ],
2362 storage
=> get_standard_option
('pve-storage-id', {
2363 description
=> "Target storage.",
2364 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2368 description
=> "Target Format.",
2369 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2374 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2380 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2388 description
=> "the task ID.",
2393 my $rpcenv = PVE
::RPCEnvironment
::get
();
2395 my $authuser = $rpcenv->get_user();
2397 my $node = extract_param
($param, 'node');
2399 my $vmid = extract_param
($param, 'vmid');
2401 my $digest = extract_param
($param, 'digest');
2403 my $disk = extract_param
($param, 'disk');
2405 my $storeid = extract_param
($param, 'storage');
2407 my $format = extract_param
($param, 'format');
2409 my $storecfg = PVE
::Storage
::config
();
2411 my $updatefn = sub {
2413 my $conf = PVE
::QemuServer
::load_config
($vmid);
2415 die "checksum missmatch (file change by other user?)\n"
2416 if $digest && $digest ne $conf->{digest
};
2418 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2420 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2422 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2424 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2427 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2428 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2432 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2433 (!$format || !$oldfmt || $oldfmt eq $format);
2435 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2437 my $running = PVE
::QemuServer
::check_running
($vmid);
2439 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2443 my $newvollist = [];
2446 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2448 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2449 $vmid, $storeid, $format, 1, $newvollist);
2451 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2453 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2455 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2458 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2459 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2466 foreach my $volid (@$newvollist) {
2467 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2470 die "storage migration failed: $err";
2473 if ($param->{delete}) {
2474 if (PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, undef, $old_volid)) {
2475 warn "volume $old_volid still has snapshots, can't delete it\n";
2476 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2477 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2479 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2485 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2488 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2491 __PACKAGE__-
>register_method({
2492 name
=> 'migrate_vm',
2493 path
=> '{vmid}/migrate',
2497 description
=> "Migrate virtual machine. Creates a new migration task.",
2499 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2502 additionalProperties
=> 0,
2504 node
=> get_standard_option
('pve-node'),
2505 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2506 target
=> get_standard_option
('pve-node', {
2507 description
=> "Target node.",
2508 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2512 description
=> "Use online/live migration.",
2517 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2524 description
=> "the task ID.",
2529 my $rpcenv = PVE
::RPCEnvironment
::get
();
2531 my $authuser = $rpcenv->get_user();
2533 my $target = extract_param
($param, 'target');
2535 my $localnode = PVE
::INotify
::nodename
();
2536 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2538 PVE
::Cluster
::check_cfs_quorum
();
2540 PVE
::Cluster
::check_node_exists
($target);
2542 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2544 my $vmid = extract_param
($param, 'vmid');
2546 raise_param_exc
({ force
=> "Only root may use this option." })
2547 if $param->{force
} && $authuser ne 'root@pam';
2550 my $conf = PVE
::QemuServer
::load_config
($vmid);
2552 # try to detect errors early
2554 PVE
::QemuServer
::check_lock
($conf);
2556 if (PVE
::QemuServer
::check_running
($vmid)) {
2557 die "cant migrate running VM without --online\n"
2558 if !$param->{online
};
2561 my $storecfg = PVE
::Storage
::config
();
2562 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2564 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2569 my $service = "vm:$vmid";
2571 my $cmd = ['ha-manager', 'migrate', $service, $target];
2573 print "Executing HA migrate for VM $vmid to node $target\n";
2575 PVE
::Tools
::run_command
($cmd);
2580 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2587 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2590 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2595 __PACKAGE__-
>register_method({
2597 path
=> '{vmid}/monitor',
2601 description
=> "Execute Qemu monitor commands.",
2603 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2606 additionalProperties
=> 0,
2608 node
=> get_standard_option
('pve-node'),
2609 vmid
=> get_standard_option
('pve-vmid'),
2612 description
=> "The monitor command.",
2616 returns
=> { type
=> 'string'},
2620 my $vmid = $param->{vmid
};
2622 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2626 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2628 $res = "ERROR: $@" if $@;
2633 __PACKAGE__-
>register_method({
2634 name
=> 'resize_vm',
2635 path
=> '{vmid}/resize',
2639 description
=> "Extend volume size.",
2641 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2644 additionalProperties
=> 0,
2646 node
=> get_standard_option
('pve-node'),
2647 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2648 skiplock
=> get_standard_option
('skiplock'),
2651 description
=> "The disk you want to resize.",
2652 enum
=> [PVE
::QemuServer
::disknames
()],
2656 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2657 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.",
2661 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2667 returns
=> { type
=> 'null'},
2671 my $rpcenv = PVE
::RPCEnvironment
::get
();
2673 my $authuser = $rpcenv->get_user();
2675 my $node = extract_param
($param, 'node');
2677 my $vmid = extract_param
($param, 'vmid');
2679 my $digest = extract_param
($param, 'digest');
2681 my $disk = extract_param
($param, 'disk');
2683 my $sizestr = extract_param
($param, 'size');
2685 my $skiplock = extract_param
($param, 'skiplock');
2686 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2687 if $skiplock && $authuser ne 'root@pam';
2689 my $storecfg = PVE
::Storage
::config
();
2691 my $updatefn = sub {
2693 my $conf = PVE
::QemuServer
::load_config
($vmid);
2695 die "checksum missmatch (file change by other user?)\n"
2696 if $digest && $digest ne $conf->{digest
};
2697 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2699 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2701 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2703 my (undef, undef, undef, undef, undef, undef, $format) =
2704 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2706 die "can't resize volume: $disk if snapshot exists\n"
2707 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2709 my $volid = $drive->{file
};
2711 die "disk '$disk' has no associated volume\n" if !$volid;
2713 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2715 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2717 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2719 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2721 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2722 my ($ext, $newsize, $unit) = ($1, $2, $4);
2725 $newsize = $newsize * 1024;
2726 } elsif ($unit eq 'M') {
2727 $newsize = $newsize * 1024 * 1024;
2728 } elsif ($unit eq 'G') {
2729 $newsize = $newsize * 1024 * 1024 * 1024;
2730 } elsif ($unit eq 'T') {
2731 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2734 $newsize += $size if $ext;
2735 $newsize = int($newsize);
2737 die "unable to skrink disk size\n" if $newsize < $size;
2739 return if $size == $newsize;
2741 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2743 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2745 $drive->{size
} = $newsize;
2746 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2748 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2751 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2755 __PACKAGE__-
>register_method({
2756 name
=> 'snapshot_list',
2757 path
=> '{vmid}/snapshot',
2759 description
=> "List all snapshots.",
2761 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2764 protected
=> 1, # qemu pid files are only readable by root
2766 additionalProperties
=> 0,
2768 vmid
=> get_standard_option
('pve-vmid'),
2769 node
=> get_standard_option
('pve-node'),
2778 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2783 my $vmid = $param->{vmid
};
2785 my $conf = PVE
::QemuServer
::load_config
($vmid);
2786 my $snaphash = $conf->{snapshots
} || {};
2790 foreach my $name (keys %$snaphash) {
2791 my $d = $snaphash->{$name};
2794 snaptime
=> $d->{snaptime
} || 0,
2795 vmstate
=> $d->{vmstate
} ?
1 : 0,
2796 description
=> $d->{description
} || '',
2798 $item->{parent
} = $d->{parent
} if $d->{parent
};
2799 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2803 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2804 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2805 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2807 push @$res, $current;
2812 __PACKAGE__-
>register_method({
2814 path
=> '{vmid}/snapshot',
2818 description
=> "Snapshot a VM.",
2820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2823 additionalProperties
=> 0,
2825 node
=> get_standard_option
('pve-node'),
2826 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2827 snapname
=> get_standard_option
('pve-snapshot-name'),
2831 description
=> "Save the vmstate",
2836 description
=> "A textual description or comment.",
2842 description
=> "the task ID.",
2847 my $rpcenv = PVE
::RPCEnvironment
::get
();
2849 my $authuser = $rpcenv->get_user();
2851 my $node = extract_param
($param, 'node');
2853 my $vmid = extract_param
($param, 'vmid');
2855 my $snapname = extract_param
($param, 'snapname');
2857 die "unable to use snapshot name 'current' (reserved name)\n"
2858 if $snapname eq 'current';
2861 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2862 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2863 $param->{description
});
2866 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2869 __PACKAGE__-
>register_method({
2870 name
=> 'snapshot_cmd_idx',
2871 path
=> '{vmid}/snapshot/{snapname}',
2878 additionalProperties
=> 0,
2880 vmid
=> get_standard_option
('pve-vmid'),
2881 node
=> get_standard_option
('pve-node'),
2882 snapname
=> get_standard_option
('pve-snapshot-name'),
2891 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2898 push @$res, { cmd
=> 'rollback' };
2899 push @$res, { cmd
=> 'config' };
2904 __PACKAGE__-
>register_method({
2905 name
=> 'update_snapshot_config',
2906 path
=> '{vmid}/snapshot/{snapname}/config',
2910 description
=> "Update snapshot metadata.",
2912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2915 additionalProperties
=> 0,
2917 node
=> get_standard_option
('pve-node'),
2918 vmid
=> get_standard_option
('pve-vmid'),
2919 snapname
=> get_standard_option
('pve-snapshot-name'),
2923 description
=> "A textual description or comment.",
2927 returns
=> { type
=> 'null' },
2931 my $rpcenv = PVE
::RPCEnvironment
::get
();
2933 my $authuser = $rpcenv->get_user();
2935 my $vmid = extract_param
($param, 'vmid');
2937 my $snapname = extract_param
($param, 'snapname');
2939 return undef if !defined($param->{description
});
2941 my $updatefn = sub {
2943 my $conf = PVE
::QemuServer
::load_config
($vmid);
2945 PVE
::QemuServer
::check_lock
($conf);
2947 my $snap = $conf->{snapshots
}->{$snapname};
2949 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2951 $snap->{description
} = $param->{description
} if defined($param->{description
});
2953 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2956 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2961 __PACKAGE__-
>register_method({
2962 name
=> 'get_snapshot_config',
2963 path
=> '{vmid}/snapshot/{snapname}/config',
2966 description
=> "Get snapshot configuration",
2968 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2971 additionalProperties
=> 0,
2973 node
=> get_standard_option
('pve-node'),
2974 vmid
=> get_standard_option
('pve-vmid'),
2975 snapname
=> get_standard_option
('pve-snapshot-name'),
2978 returns
=> { type
=> "object" },
2982 my $rpcenv = PVE
::RPCEnvironment
::get
();
2984 my $authuser = $rpcenv->get_user();
2986 my $vmid = extract_param
($param, 'vmid');
2988 my $snapname = extract_param
($param, 'snapname');
2990 my $conf = PVE
::QemuServer
::load_config
($vmid);
2992 my $snap = $conf->{snapshots
}->{$snapname};
2994 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2999 __PACKAGE__-
>register_method({
3001 path
=> '{vmid}/snapshot/{snapname}/rollback',
3005 description
=> "Rollback VM state to specified snapshot.",
3007 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3010 additionalProperties
=> 0,
3012 node
=> get_standard_option
('pve-node'),
3013 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3014 snapname
=> get_standard_option
('pve-snapshot-name'),
3019 description
=> "the task ID.",
3024 my $rpcenv = PVE
::RPCEnvironment
::get
();
3026 my $authuser = $rpcenv->get_user();
3028 my $node = extract_param
($param, 'node');
3030 my $vmid = extract_param
($param, 'vmid');
3032 my $snapname = extract_param
($param, 'snapname');
3035 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3036 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3039 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3042 __PACKAGE__-
>register_method({
3043 name
=> 'delsnapshot',
3044 path
=> '{vmid}/snapshot/{snapname}',
3048 description
=> "Delete a VM snapshot.",
3050 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3053 additionalProperties
=> 0,
3055 node
=> get_standard_option
('pve-node'),
3056 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3057 snapname
=> get_standard_option
('pve-snapshot-name'),
3061 description
=> "For removal from config file, even if removing disk snapshots fails.",
3067 description
=> "the task ID.",
3072 my $rpcenv = PVE
::RPCEnvironment
::get
();
3074 my $authuser = $rpcenv->get_user();
3076 my $node = extract_param
($param, 'node');
3078 my $vmid = extract_param
($param, 'vmid');
3080 my $snapname = extract_param
($param, 'snapname');
3083 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3084 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3087 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3090 __PACKAGE__-
>register_method({
3092 path
=> '{vmid}/template',
3096 description
=> "Create a Template.",
3098 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3099 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3102 additionalProperties
=> 0,
3104 node
=> get_standard_option
('pve-node'),
3105 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3109 description
=> "If you want to convert only 1 disk to base image.",
3110 enum
=> [PVE
::QemuServer
::disknames
()],
3115 returns
=> { type
=> 'null'},
3119 my $rpcenv = PVE
::RPCEnvironment
::get
();
3121 my $authuser = $rpcenv->get_user();
3123 my $node = extract_param
($param, 'node');
3125 my $vmid = extract_param
($param, 'vmid');
3127 my $disk = extract_param
($param, 'disk');
3129 my $updatefn = sub {
3131 my $conf = PVE
::QemuServer
::load_config
($vmid);
3133 PVE
::QemuServer
::check_lock
($conf);
3135 die "unable to create template, because VM contains snapshots\n"
3136 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3138 die "you can't convert a template to a template\n"
3139 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3141 die "you can't convert a VM to template if VM is running\n"
3142 if PVE
::QemuServer
::check_running
($vmid);
3145 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3148 $conf->{template
} = 1;
3149 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3151 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3154 PVE
::QemuServer
::lock_config
($vmid, $updatefn);