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 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2475 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2476 if ($used_paths->{$path}){
2477 warn "volume $old_volid have snapshots. Can't delete it\n";
2478 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2479 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2481 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2487 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2490 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2493 __PACKAGE__-
>register_method({
2494 name
=> 'migrate_vm',
2495 path
=> '{vmid}/migrate',
2499 description
=> "Migrate virtual machine. Creates a new migration task.",
2501 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2504 additionalProperties
=> 0,
2506 node
=> get_standard_option
('pve-node'),
2507 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2508 target
=> get_standard_option
('pve-node', {
2509 description
=> "Target node.",
2510 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2514 description
=> "Use online/live migration.",
2519 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2526 description
=> "the task ID.",
2531 my $rpcenv = PVE
::RPCEnvironment
::get
();
2533 my $authuser = $rpcenv->get_user();
2535 my $target = extract_param
($param, 'target');
2537 my $localnode = PVE
::INotify
::nodename
();
2538 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2540 PVE
::Cluster
::check_cfs_quorum
();
2542 PVE
::Cluster
::check_node_exists
($target);
2544 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2546 my $vmid = extract_param
($param, 'vmid');
2548 raise_param_exc
({ force
=> "Only root may use this option." })
2549 if $param->{force
} && $authuser ne 'root@pam';
2552 my $conf = PVE
::QemuServer
::load_config
($vmid);
2554 # try to detect errors early
2556 PVE
::QemuServer
::check_lock
($conf);
2558 if (PVE
::QemuServer
::check_running
($vmid)) {
2559 die "cant migrate running VM without --online\n"
2560 if !$param->{online
};
2563 my $storecfg = PVE
::Storage
::config
();
2564 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2566 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2571 my $service = "vm:$vmid";
2573 my $cmd = ['ha-manager', 'migrate', $service, $target];
2575 print "Executing HA migrate for VM $vmid to node $target\n";
2577 PVE
::Tools
::run_command
($cmd);
2582 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2589 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2592 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2597 __PACKAGE__-
>register_method({
2599 path
=> '{vmid}/monitor',
2603 description
=> "Execute Qemu monitor commands.",
2605 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2608 additionalProperties
=> 0,
2610 node
=> get_standard_option
('pve-node'),
2611 vmid
=> get_standard_option
('pve-vmid'),
2614 description
=> "The monitor command.",
2618 returns
=> { type
=> 'string'},
2622 my $vmid = $param->{vmid
};
2624 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2628 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2630 $res = "ERROR: $@" if $@;
2635 __PACKAGE__-
>register_method({
2636 name
=> 'resize_vm',
2637 path
=> '{vmid}/resize',
2641 description
=> "Extend volume size.",
2643 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2646 additionalProperties
=> 0,
2648 node
=> get_standard_option
('pve-node'),
2649 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2650 skiplock
=> get_standard_option
('skiplock'),
2653 description
=> "The disk you want to resize.",
2654 enum
=> [PVE
::QemuServer
::disknames
()],
2658 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2659 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.",
2663 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2669 returns
=> { type
=> 'null'},
2673 my $rpcenv = PVE
::RPCEnvironment
::get
();
2675 my $authuser = $rpcenv->get_user();
2677 my $node = extract_param
($param, 'node');
2679 my $vmid = extract_param
($param, 'vmid');
2681 my $digest = extract_param
($param, 'digest');
2683 my $disk = extract_param
($param, 'disk');
2685 my $sizestr = extract_param
($param, 'size');
2687 my $skiplock = extract_param
($param, 'skiplock');
2688 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2689 if $skiplock && $authuser ne 'root@pam';
2691 my $storecfg = PVE
::Storage
::config
();
2693 my $updatefn = sub {
2695 my $conf = PVE
::QemuServer
::load_config
($vmid);
2697 die "checksum missmatch (file change by other user?)\n"
2698 if $digest && $digest ne $conf->{digest
};
2699 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2701 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2703 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2705 my (undef, undef, undef, undef, undef, undef, $format) =
2706 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2708 die "can't resize volume: $disk if snapshot exists\n"
2709 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2711 my $volid = $drive->{file
};
2713 die "disk '$disk' has no associated volume\n" if !$volid;
2715 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2717 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2719 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2721 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2723 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2724 my ($ext, $newsize, $unit) = ($1, $2, $4);
2727 $newsize = $newsize * 1024;
2728 } elsif ($unit eq 'M') {
2729 $newsize = $newsize * 1024 * 1024;
2730 } elsif ($unit eq 'G') {
2731 $newsize = $newsize * 1024 * 1024 * 1024;
2732 } elsif ($unit eq 'T') {
2733 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2736 $newsize += $size if $ext;
2737 $newsize = int($newsize);
2739 die "unable to skrink disk size\n" if $newsize < $size;
2741 return if $size == $newsize;
2743 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2745 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2747 $drive->{size
} = $newsize;
2748 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2750 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2753 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2757 __PACKAGE__-
>register_method({
2758 name
=> 'snapshot_list',
2759 path
=> '{vmid}/snapshot',
2761 description
=> "List all snapshots.",
2763 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2766 protected
=> 1, # qemu pid files are only readable by root
2768 additionalProperties
=> 0,
2770 vmid
=> get_standard_option
('pve-vmid'),
2771 node
=> get_standard_option
('pve-node'),
2780 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2785 my $vmid = $param->{vmid
};
2787 my $conf = PVE
::QemuServer
::load_config
($vmid);
2788 my $snaphash = $conf->{snapshots
} || {};
2792 foreach my $name (keys %$snaphash) {
2793 my $d = $snaphash->{$name};
2796 snaptime
=> $d->{snaptime
} || 0,
2797 vmstate
=> $d->{vmstate
} ?
1 : 0,
2798 description
=> $d->{description
} || '',
2800 $item->{parent
} = $d->{parent
} if $d->{parent
};
2801 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2805 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2806 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2807 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2809 push @$res, $current;
2814 __PACKAGE__-
>register_method({
2816 path
=> '{vmid}/snapshot',
2820 description
=> "Snapshot a VM.",
2822 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2825 additionalProperties
=> 0,
2827 node
=> get_standard_option
('pve-node'),
2828 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2829 snapname
=> get_standard_option
('pve-snapshot-name'),
2833 description
=> "Save the vmstate",
2838 description
=> "A textual description or comment.",
2844 description
=> "the task ID.",
2849 my $rpcenv = PVE
::RPCEnvironment
::get
();
2851 my $authuser = $rpcenv->get_user();
2853 my $node = extract_param
($param, 'node');
2855 my $vmid = extract_param
($param, 'vmid');
2857 my $snapname = extract_param
($param, 'snapname');
2859 die "unable to use snapshot name 'current' (reserved name)\n"
2860 if $snapname eq 'current';
2863 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2864 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2865 $param->{description
});
2868 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2871 __PACKAGE__-
>register_method({
2872 name
=> 'snapshot_cmd_idx',
2873 path
=> '{vmid}/snapshot/{snapname}',
2880 additionalProperties
=> 0,
2882 vmid
=> get_standard_option
('pve-vmid'),
2883 node
=> get_standard_option
('pve-node'),
2884 snapname
=> get_standard_option
('pve-snapshot-name'),
2893 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2900 push @$res, { cmd
=> 'rollback' };
2901 push @$res, { cmd
=> 'config' };
2906 __PACKAGE__-
>register_method({
2907 name
=> 'update_snapshot_config',
2908 path
=> '{vmid}/snapshot/{snapname}/config',
2912 description
=> "Update snapshot metadata.",
2914 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2917 additionalProperties
=> 0,
2919 node
=> get_standard_option
('pve-node'),
2920 vmid
=> get_standard_option
('pve-vmid'),
2921 snapname
=> get_standard_option
('pve-snapshot-name'),
2925 description
=> "A textual description or comment.",
2929 returns
=> { type
=> 'null' },
2933 my $rpcenv = PVE
::RPCEnvironment
::get
();
2935 my $authuser = $rpcenv->get_user();
2937 my $vmid = extract_param
($param, 'vmid');
2939 my $snapname = extract_param
($param, 'snapname');
2941 return undef if !defined($param->{description
});
2943 my $updatefn = sub {
2945 my $conf = PVE
::QemuServer
::load_config
($vmid);
2947 PVE
::QemuServer
::check_lock
($conf);
2949 my $snap = $conf->{snapshots
}->{$snapname};
2951 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2953 $snap->{description
} = $param->{description
} if defined($param->{description
});
2955 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2958 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2963 __PACKAGE__-
>register_method({
2964 name
=> 'get_snapshot_config',
2965 path
=> '{vmid}/snapshot/{snapname}/config',
2968 description
=> "Get snapshot configuration",
2970 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2973 additionalProperties
=> 0,
2975 node
=> get_standard_option
('pve-node'),
2976 vmid
=> get_standard_option
('pve-vmid'),
2977 snapname
=> get_standard_option
('pve-snapshot-name'),
2980 returns
=> { type
=> "object" },
2984 my $rpcenv = PVE
::RPCEnvironment
::get
();
2986 my $authuser = $rpcenv->get_user();
2988 my $vmid = extract_param
($param, 'vmid');
2990 my $snapname = extract_param
($param, 'snapname');
2992 my $conf = PVE
::QemuServer
::load_config
($vmid);
2994 my $snap = $conf->{snapshots
}->{$snapname};
2996 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3001 __PACKAGE__-
>register_method({
3003 path
=> '{vmid}/snapshot/{snapname}/rollback',
3007 description
=> "Rollback VM state to specified snapshot.",
3009 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3012 additionalProperties
=> 0,
3014 node
=> get_standard_option
('pve-node'),
3015 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3016 snapname
=> get_standard_option
('pve-snapshot-name'),
3021 description
=> "the task ID.",
3026 my $rpcenv = PVE
::RPCEnvironment
::get
();
3028 my $authuser = $rpcenv->get_user();
3030 my $node = extract_param
($param, 'node');
3032 my $vmid = extract_param
($param, 'vmid');
3034 my $snapname = extract_param
($param, 'snapname');
3037 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3038 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3041 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3044 __PACKAGE__-
>register_method({
3045 name
=> 'delsnapshot',
3046 path
=> '{vmid}/snapshot/{snapname}',
3050 description
=> "Delete a VM snapshot.",
3052 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3055 additionalProperties
=> 0,
3057 node
=> get_standard_option
('pve-node'),
3058 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3059 snapname
=> get_standard_option
('pve-snapshot-name'),
3063 description
=> "For removal from config file, even if removing disk snapshots fails.",
3069 description
=> "the task ID.",
3074 my $rpcenv = PVE
::RPCEnvironment
::get
();
3076 my $authuser = $rpcenv->get_user();
3078 my $node = extract_param
($param, 'node');
3080 my $vmid = extract_param
($param, 'vmid');
3082 my $snapname = extract_param
($param, 'snapname');
3085 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3086 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3089 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3092 __PACKAGE__-
>register_method({
3094 path
=> '{vmid}/template',
3098 description
=> "Create a Template.",
3100 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3101 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3104 additionalProperties
=> 0,
3106 node
=> get_standard_option
('pve-node'),
3107 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3111 description
=> "If you want to convert only 1 disk to base image.",
3112 enum
=> [PVE
::QemuServer
::disknames
()],
3117 returns
=> { type
=> 'null'},
3121 my $rpcenv = PVE
::RPCEnvironment
::get
();
3123 my $authuser = $rpcenv->get_user();
3125 my $node = extract_param
($param, 'node');
3127 my $vmid = extract_param
($param, 'vmid');
3129 my $disk = extract_param
($param, 'disk');
3131 my $updatefn = sub {
3133 my $conf = PVE
::QemuServer
::load_config
($vmid);
3135 PVE
::QemuServer
::check_lock
($conf);
3137 die "unable to create template, because VM contains snapshots\n"
3138 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3140 die "you can't convert a template to a template\n"
3141 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3143 die "you can't convert a VM to template if VM is running\n"
3144 if PVE
::QemuServer
::check_running
($vmid);
3147 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3150 $conf->{template
} = 1;
3151 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3153 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3156 PVE
::QemuServer
::lock_config
($vmid, $updatefn);