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
;
24 use PVE
::HA
::Env
::PVE2
;
27 use Data
::Dumper
; # fixme: remove
29 use base
qw(PVE::RESTHandler);
31 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.";
33 my $resolve_cdrom_alias = sub {
36 if (my $value = $param->{cdrom
}) {
37 $value .= ",media=cdrom" if $value !~ m/media=/;
38 $param->{ide2
} = $value;
39 delete $param->{cdrom
};
43 my $check_storage_access = sub {
44 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
46 PVE
::QemuServer
::foreach_drive
($settings, sub {
47 my ($ds, $drive) = @_;
49 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
51 my $volid = $drive->{file
};
53 if (!$volid || $volid eq 'none') {
55 } elsif ($isCDROM && ($volid eq 'cdrom')) {
56 $rpcenv->check($authuser, "/", ['Sys.Console']);
57 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
58 my ($storeid, $size) = ($2 || $default_storage, $3);
59 die "no storage ID specified (and no default storage)\n" if !$storeid;
60 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
62 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
67 my $check_storage_access_clone = sub {
68 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
72 PVE
::QemuServer
::foreach_drive
($conf, sub {
73 my ($ds, $drive) = @_;
75 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
77 my $volid = $drive->{file
};
79 return if !$volid || $volid eq 'none';
82 if ($volid eq 'cdrom') {
83 $rpcenv->check($authuser, "/", ['Sys.Console']);
85 # we simply allow access
86 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
87 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
88 $sharedvm = 0 if !$scfg->{shared
};
92 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
93 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
94 $sharedvm = 0 if !$scfg->{shared
};
96 $sid = $storage if $storage;
97 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
104 # Note: $pool is only needed when creating a VM, because pool permissions
105 # are automatically inherited if VM already exists inside a pool.
106 my $create_disks = sub {
107 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
112 PVE
::QemuServer
::foreach_drive
($settings, sub {
113 my ($ds, $disk) = @_;
115 my $volid = $disk->{file
};
117 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
118 delete $disk->{size
};
119 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
120 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
121 my ($storeid, $size) = ($2 || $default_storage, $3);
122 die "no storage ID specified (and no default storage)\n" if !$storeid;
123 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
124 my $fmt = $disk->{format
} || $defformat;
125 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
126 $fmt, undef, $size*1024*1024);
127 $disk->{file
} = $volid;
128 $disk->{size
} = $size*1024*1024*1024;
129 push @$vollist, $volid;
130 delete $disk->{format
}; # no longer needed
131 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
134 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
136 my $volid_is_new = 1;
139 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
140 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
145 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
147 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
149 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
151 die "volume $volid does not exists\n" if !$size;
153 $disk->{size
} = $size;
156 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
160 # free allocated images on error
162 syslog
('err', "VM $vmid creating disks failed");
163 foreach my $volid (@$vollist) {
164 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
170 # modify vm config if everything went well
171 foreach my $ds (keys %$res) {
172 $conf->{$ds} = $res->{$ds};
178 my $check_vm_modify_config_perm = sub {
179 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
181 return 1 if $authuser eq 'root@pam';
183 foreach my $opt (@$key_list) {
184 # disk checks need to be done somewhere else
185 next if PVE
::QemuServer
::valid_drivename
($opt);
187 if ($opt eq 'sockets' || $opt eq 'cores' ||
188 $opt eq 'cpu' || $opt eq 'smp' || $opt eq 'vcpus' ||
189 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
190 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
191 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
192 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
193 } elsif ($opt eq 'args' || $opt eq 'lock') {
194 die "only root can set '$opt' config\n";
195 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
196 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
197 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
198 } elsif ($opt =~ m/^net\d+$/) {
199 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
201 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
208 my $check_protection = sub {
209 my ($vm_conf, $err_msg) = @_;
211 if ($vm_conf->{protection
}) {
212 die "$err_msg - protection mode enabled\n";
216 __PACKAGE__-
>register_method({
220 description
=> "Virtual machine index (per node).",
222 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
226 protected
=> 1, # qemu pid files are only readable by root
228 additionalProperties
=> 0,
230 node
=> get_standard_option
('pve-node'),
239 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
244 my $rpcenv = PVE
::RPCEnvironment
::get
();
245 my $authuser = $rpcenv->get_user();
247 my $vmstatus = PVE
::QemuServer
::vmstatus
();
250 foreach my $vmid (keys %$vmstatus) {
251 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
253 my $data = $vmstatus->{$vmid};
254 $data->{vmid
} = int($vmid);
263 __PACKAGE__-
>register_method({
267 description
=> "Create or restore a virtual machine.",
269 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
270 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
271 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
272 user
=> 'all', # check inside
277 additionalProperties
=> 0,
278 properties
=> PVE
::QemuServer
::json_config_properties
(
280 node
=> get_standard_option
('pve-node'),
281 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
283 description
=> "The backup file.",
287 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
289 storage
=> get_standard_option
('pve-storage-id', {
290 description
=> "Default storage.",
292 completion
=> \
&PVE
::QemuServer
::complete_storage
,
297 description
=> "Allow to overwrite existing VM.",
298 requires
=> 'archive',
303 description
=> "Assign a unique random ethernet address.",
304 requires
=> 'archive',
308 type
=> 'string', format
=> 'pve-poolid',
309 description
=> "Add the VM to the specified pool.",
319 my $rpcenv = PVE
::RPCEnvironment
::get
();
321 my $authuser = $rpcenv->get_user();
323 my $node = extract_param
($param, 'node');
325 my $vmid = extract_param
($param, 'vmid');
327 my $archive = extract_param
($param, 'archive');
329 my $storage = extract_param
($param, 'storage');
331 my $force = extract_param
($param, 'force');
333 my $unique = extract_param
($param, 'unique');
335 my $pool = extract_param
($param, 'pool');
337 my $filename = PVE
::QemuServer
::config_file
($vmid);
339 my $storecfg = PVE
::Storage
::config
();
341 PVE
::Cluster
::check_cfs_quorum
();
343 if (defined($pool)) {
344 $rpcenv->check_pool_exist($pool);
347 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
348 if defined($storage);
350 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
352 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
354 } elsif ($archive && $force && (-f
$filename) &&
355 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
356 # OK: user has VM.Backup permissions, and want to restore an existing VM
362 &$resolve_cdrom_alias($param);
364 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
366 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
368 foreach my $opt (keys %$param) {
369 if (PVE
::QemuServer
::valid_drivename
($opt)) {
370 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
371 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
373 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
374 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
378 PVE
::QemuServer
::add_random_macs
($param);
380 my $keystr = join(' ', keys %$param);
381 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
383 if ($archive eq '-') {
384 die "pipe requires cli environment\n"
385 if $rpcenv->{type
} ne 'cli';
387 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
388 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
392 my $restorefn = sub {
393 my $vmlist = PVE
::Cluster
::get_vmlist
();
394 if ($vmlist->{ids
}->{$vmid}) {
395 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
396 if ($current_node eq $node) {
397 my $conf = PVE
::QemuServer
::load_config
($vmid);
399 &$check_protection($conf, "unable to restore VM $vmid");
401 die "unable to restore vm $vmid - config file already exists\n"
404 die "unable to restore vm $vmid - vm is running\n"
405 if PVE
::QemuServer
::check_running
($vmid);
407 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
412 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
415 unique
=> $unique });
417 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
420 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
426 PVE
::Cluster
::check_vmid_unused
($vmid);
436 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
438 # try to be smart about bootdisk
439 my @disks = PVE
::QemuServer
::disknames
();
441 foreach my $ds (reverse @disks) {
442 next if !$conf->{$ds};
443 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
444 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
448 if (!$conf->{bootdisk
} && $firstdisk) {
449 $conf->{bootdisk
} = $firstdisk;
452 # auto generate uuid if user did not specify smbios1 option
453 if (!$conf->{smbios1
}) {
454 my ($uuid, $uuid_str);
455 UUID
::generate
($uuid);
456 UUID
::unparse
($uuid, $uuid_str);
457 $conf->{smbios1
} = "uuid=$uuid_str";
460 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
466 foreach my $volid (@$vollist) {
467 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
470 die "create failed - $err";
473 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
476 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
479 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
482 __PACKAGE__-
>register_method({
487 description
=> "Directory index",
492 additionalProperties
=> 0,
494 node
=> get_standard_option
('pve-node'),
495 vmid
=> get_standard_option
('pve-vmid'),
503 subdir
=> { type
=> 'string' },
506 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
512 { subdir
=> 'config' },
513 { subdir
=> 'pending' },
514 { subdir
=> 'status' },
515 { subdir
=> 'unlink' },
516 { subdir
=> 'vncproxy' },
517 { subdir
=> 'migrate' },
518 { subdir
=> 'resize' },
519 { subdir
=> 'move' },
521 { subdir
=> 'rrddata' },
522 { subdir
=> 'monitor' },
523 { subdir
=> 'snapshot' },
524 { subdir
=> 'spiceproxy' },
525 { subdir
=> 'sendkey' },
526 { subdir
=> 'firewall' },
532 __PACKAGE__-
>register_method ({
533 subclass
=> "PVE::API2::Firewall::VM",
534 path
=> '{vmid}/firewall',
537 __PACKAGE__-
>register_method({
539 path
=> '{vmid}/rrd',
541 protected
=> 1, # fixme: can we avoid that?
543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
545 description
=> "Read VM RRD statistics (returns PNG)",
547 additionalProperties
=> 0,
549 node
=> get_standard_option
('pve-node'),
550 vmid
=> get_standard_option
('pve-vmid'),
552 description
=> "Specify the time frame you are interested in.",
554 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
557 description
=> "The list of datasources you want to display.",
558 type
=> 'string', format
=> 'pve-configid-list',
561 description
=> "The RRD consolidation function",
563 enum
=> [ 'AVERAGE', 'MAX' ],
571 filename
=> { type
=> 'string' },
577 return PVE
::Cluster
::create_rrd_graph
(
578 "pve2-vm/$param->{vmid}", $param->{timeframe
},
579 $param->{ds
}, $param->{cf
});
583 __PACKAGE__-
>register_method({
585 path
=> '{vmid}/rrddata',
587 protected
=> 1, # fixme: can we avoid that?
589 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
591 description
=> "Read VM RRD statistics",
593 additionalProperties
=> 0,
595 node
=> get_standard_option
('pve-node'),
596 vmid
=> get_standard_option
('pve-vmid'),
598 description
=> "Specify the time frame you are interested in.",
600 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
603 description
=> "The RRD consolidation function",
605 enum
=> [ 'AVERAGE', 'MAX' ],
620 return PVE
::Cluster
::create_rrd_data
(
621 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
625 __PACKAGE__-
>register_method({
627 path
=> '{vmid}/config',
630 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
632 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
635 additionalProperties
=> 0,
637 node
=> get_standard_option
('pve-node'),
638 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
640 description
=> "Get current values (instead of pending values).",
652 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
659 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
661 delete $conf->{snapshots
};
663 if (!$param->{current
}) {
664 foreach my $opt (keys %{$conf->{pending
}}) {
665 next if $opt eq 'delete';
666 my $value = $conf->{pending
}->{$opt};
667 next if ref($value); # just to be sure
668 $conf->{$opt} = $value;
670 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
671 foreach my $opt (keys %$pending_delete_hash) {
672 delete $conf->{$opt} if $conf->{$opt};
676 delete $conf->{pending
};
681 __PACKAGE__-
>register_method({
682 name
=> 'vm_pending',
683 path
=> '{vmid}/pending',
686 description
=> "Get virtual machine configuration, including pending changes.",
688 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
691 additionalProperties
=> 0,
693 node
=> get_standard_option
('pve-node'),
694 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
703 description
=> "Configuration option name.",
707 description
=> "Current value.",
712 description
=> "Pending value.",
717 description
=> "Indicates a pending delete request if present and not 0. " .
718 "The value 2 indicates a force-delete request.",
730 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
732 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
736 foreach my $opt (keys %$conf) {
737 next if ref($conf->{$opt});
738 my $item = { key
=> $opt };
739 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
740 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
741 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
745 foreach my $opt (keys %{$conf->{pending
}}) {
746 next if $opt eq 'delete';
747 next if ref($conf->{pending
}->{$opt}); # just to be sure
748 next if defined($conf->{$opt});
749 my $item = { key
=> $opt };
750 $item->{pending
} = $conf->{pending
}->{$opt};
754 while (my ($opt, $force) = each %$pending_delete_hash) {
755 next if $conf->{pending
}->{$opt}; # just to be sure
756 next if $conf->{$opt};
757 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
764 # POST/PUT {vmid}/config implementation
766 # The original API used PUT (idempotent) an we assumed that all operations
767 # are fast. But it turned out that almost any configuration change can
768 # involve hot-plug actions, or disk alloc/free. Such actions can take long
769 # time to complete and have side effects (not idempotent).
771 # The new implementation uses POST and forks a worker process. We added
772 # a new option 'background_delay'. If specified we wait up to
773 # 'background_delay' second for the worker task to complete. It returns null
774 # if the task is finished within that time, else we return the UPID.
776 my $update_vm_api = sub {
777 my ($param, $sync) = @_;
779 my $rpcenv = PVE
::RPCEnvironment
::get
();
781 my $authuser = $rpcenv->get_user();
783 my $node = extract_param
($param, 'node');
785 my $vmid = extract_param
($param, 'vmid');
787 my $digest = extract_param
($param, 'digest');
789 my $background_delay = extract_param
($param, 'background_delay');
791 my @paramarr = (); # used for log message
792 foreach my $key (keys %$param) {
793 push @paramarr, "-$key", $param->{$key};
796 my $skiplock = extract_param
($param, 'skiplock');
797 raise_param_exc
({ skiplock
=> "Only root may use this option." })
798 if $skiplock && $authuser ne 'root@pam';
800 my $delete_str = extract_param
($param, 'delete');
802 my $revert_str = extract_param
($param, 'revert');
804 my $force = extract_param
($param, 'force');
806 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
808 my $storecfg = PVE
::Storage
::config
();
810 my $defaults = PVE
::QemuServer
::load_defaults
();
812 &$resolve_cdrom_alias($param);
814 # now try to verify all parameters
817 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
818 if (!PVE
::QemuServer
::option_exists
($opt)) {
819 raise_param_exc
({ revert
=> "unknown option '$opt'" });
822 raise_param_exc
({ delete => "you can't use '-$opt' and " .
823 "-revert $opt' at the same time" })
824 if defined($param->{$opt});
830 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
831 $opt = 'ide2' if $opt eq 'cdrom';
833 raise_param_exc
({ delete => "you can't use '-$opt' and " .
834 "-delete $opt' at the same time" })
835 if defined($param->{$opt});
837 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
838 "-revert $opt' at the same time" })
841 if (!PVE
::QemuServer
::option_exists
($opt)) {
842 raise_param_exc
({ delete => "unknown option '$opt'" });
848 foreach my $opt (keys %$param) {
849 if (PVE
::QemuServer
::valid_drivename
($opt)) {
851 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
852 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
853 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
854 } elsif ($opt =~ m/^net(\d+)$/) {
856 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
857 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
861 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
863 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
865 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
869 my $conf = PVE
::QemuServer
::load_config
($vmid);
871 die "checksum missmatch (file change by other user?)\n"
872 if $digest && $digest ne $conf->{digest
};
874 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
876 foreach my $opt (keys %$revert) {
877 if (defined($conf->{$opt})) {
878 $param->{$opt} = $conf->{$opt};
879 } elsif (defined($conf->{pending
}->{$opt})) {
884 if ($param->{memory
} || defined($param->{balloon
})) {
885 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
886 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
888 die "balloon value too large (must be smaller than assigned memory)\n"
889 if $balloon && $balloon > $maxmem;
892 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
896 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
898 # write updates to pending section
900 my $modified = {}; # record what $option we modify
902 foreach my $opt (@delete) {
903 $modified->{$opt} = 1;
904 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
905 if ($opt =~ m/^unused/) {
906 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
907 &$check_protection($conf, "can't remove unused disk '$drive->{file}'");
908 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
909 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
910 delete $conf->{$opt};
911 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
913 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
914 &$check_protection($conf, "can't remove drive '$opt'");
915 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
916 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
917 if defined($conf->{pending
}->{$opt});
918 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
919 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
921 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
922 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
926 foreach my $opt (keys %$param) { # add/change
927 $modified->{$opt} = 1;
928 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
929 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
931 if (PVE
::QemuServer
::valid_drivename
($opt)) {
932 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
933 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
934 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
936 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
938 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
939 if defined($conf->{pending
}->{$opt});
941 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
943 $conf->{pending
}->{$opt} = $param->{$opt};
945 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
946 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
949 # remove pending changes when nothing changed
950 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
951 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
952 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
954 return if !scalar(keys %{$conf->{pending
}});
956 my $running = PVE
::QemuServer
::check_running
($vmid);
958 # apply pending changes
960 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
964 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
965 raise_param_exc
($errors) if scalar(keys %$errors);
967 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
977 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
979 if ($background_delay) {
981 # Note: It would be better to do that in the Event based HTTPServer
982 # to avoid blocking call to sleep.
984 my $end_time = time() + $background_delay;
986 my $task = PVE
::Tools
::upid_decode
($upid);
989 while (time() < $end_time) {
990 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
992 sleep(1); # this gets interrupted when child process ends
996 my $status = PVE
::Tools
::upid_read_status
($upid);
997 return undef if $status eq 'OK';
1006 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
1009 my $vm_config_perm_list = [
1014 'VM.Config.Network',
1016 'VM.Config.Options',
1019 __PACKAGE__-
>register_method({
1020 name
=> 'update_vm_async',
1021 path
=> '{vmid}/config',
1025 description
=> "Set virtual machine options (asynchrounous API).",
1027 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1030 additionalProperties
=> 0,
1031 properties
=> PVE
::QemuServer
::json_config_properties
(
1033 node
=> get_standard_option
('pve-node'),
1034 vmid
=> get_standard_option
('pve-vmid'),
1035 skiplock
=> get_standard_option
('skiplock'),
1037 type
=> 'string', format
=> 'pve-configid-list',
1038 description
=> "A list of settings you want to delete.",
1042 type
=> 'string', format
=> 'pve-configid-list',
1043 description
=> "Revert a pending change.",
1048 description
=> $opt_force_description,
1050 requires
=> 'delete',
1054 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1058 background_delay
=> {
1060 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1071 code
=> $update_vm_api,
1074 __PACKAGE__-
>register_method({
1075 name
=> 'update_vm',
1076 path
=> '{vmid}/config',
1080 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1082 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1085 additionalProperties
=> 0,
1086 properties
=> PVE
::QemuServer
::json_config_properties
(
1088 node
=> get_standard_option
('pve-node'),
1089 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1090 skiplock
=> get_standard_option
('skiplock'),
1092 type
=> 'string', format
=> 'pve-configid-list',
1093 description
=> "A list of settings you want to delete.",
1097 type
=> 'string', format
=> 'pve-configid-list',
1098 description
=> "Revert a pending change.",
1103 description
=> $opt_force_description,
1105 requires
=> 'delete',
1109 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1115 returns
=> { type
=> 'null' },
1118 &$update_vm_api($param, 1);
1124 __PACKAGE__-
>register_method({
1125 name
=> 'destroy_vm',
1130 description
=> "Destroy the vm (also delete all used/owned volumes).",
1132 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1135 additionalProperties
=> 0,
1137 node
=> get_standard_option
('pve-node'),
1138 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1139 skiplock
=> get_standard_option
('skiplock'),
1148 my $rpcenv = PVE
::RPCEnvironment
::get
();
1150 my $authuser = $rpcenv->get_user();
1152 my $vmid = $param->{vmid
};
1154 my $skiplock = $param->{skiplock
};
1155 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1156 if $skiplock && $authuser ne 'root@pam';
1159 my $conf = PVE
::QemuServer
::load_config
($vmid);
1161 my $storecfg = PVE
::Storage
::config
();
1163 &$check_protection($conf, "can't remove VM $vmid");
1165 die "unable to remove VM $vmid - used in HA resources\n"
1166 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1168 # early tests (repeat after locking)
1169 die "VM $vmid is running - destroy failed\n"
1170 if PVE
::QemuServer
::check_running
($vmid);
1175 syslog
('info', "destroy VM $vmid: $upid\n");
1177 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1179 PVE
::AccessControl
::remove_vm_access
($vmid);
1181 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1184 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1187 __PACKAGE__-
>register_method({
1189 path
=> '{vmid}/unlink',
1193 description
=> "Unlink/delete disk images.",
1195 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1198 additionalProperties
=> 0,
1200 node
=> get_standard_option
('pve-node'),
1201 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1203 type
=> 'string', format
=> 'pve-configid-list',
1204 description
=> "A list of disk IDs you want to delete.",
1208 description
=> $opt_force_description,
1213 returns
=> { type
=> 'null'},
1217 $param->{delete} = extract_param
($param, 'idlist');
1219 __PACKAGE__-
>update_vm($param);
1226 __PACKAGE__-
>register_method({
1228 path
=> '{vmid}/vncproxy',
1232 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1234 description
=> "Creates a TCP VNC proxy connections.",
1236 additionalProperties
=> 0,
1238 node
=> get_standard_option
('pve-node'),
1239 vmid
=> get_standard_option
('pve-vmid'),
1243 description
=> "starts websockify instead of vncproxy",
1248 additionalProperties
=> 0,
1250 user
=> { type
=> 'string' },
1251 ticket
=> { type
=> 'string' },
1252 cert
=> { type
=> 'string' },
1253 port
=> { type
=> 'integer' },
1254 upid
=> { type
=> 'string' },
1260 my $rpcenv = PVE
::RPCEnvironment
::get
();
1262 my $authuser = $rpcenv->get_user();
1264 my $vmid = $param->{vmid
};
1265 my $node = $param->{node
};
1266 my $websocket = $param->{websocket
};
1268 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1270 my $authpath = "/vms/$vmid";
1272 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1274 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1277 my ($remip, $family);
1280 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1281 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1282 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1283 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1285 $family = PVE
::Tools
::get_host_address_family
($node);
1288 my $port = PVE
::Tools
::next_vnc_port
($family);
1295 syslog
('info', "starting vnc proxy $upid\n");
1299 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1301 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1303 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1304 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1305 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1306 '-timeout', $timeout, '-authpath', $authpath,
1307 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1310 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1312 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1314 my $qmstr = join(' ', @$qmcmd);
1316 # also redirect stderr (else we get RFB protocol errors)
1317 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1320 PVE
::Tools
::run_command
($cmd);
1325 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1327 PVE
::Tools
::wait_for_vnc_port
($port);
1338 __PACKAGE__-
>register_method({
1339 name
=> 'vncwebsocket',
1340 path
=> '{vmid}/vncwebsocket',
1343 description
=> "You also need to pass a valid ticket (vncticket).",
1344 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1346 description
=> "Opens a weksocket for VNC traffic.",
1348 additionalProperties
=> 0,
1350 node
=> get_standard_option
('pve-node'),
1351 vmid
=> get_standard_option
('pve-vmid'),
1353 description
=> "Ticket from previous call to vncproxy.",
1358 description
=> "Port number returned by previous vncproxy call.",
1368 port
=> { type
=> 'string' },
1374 my $rpcenv = PVE
::RPCEnvironment
::get
();
1376 my $authuser = $rpcenv->get_user();
1378 my $vmid = $param->{vmid
};
1379 my $node = $param->{node
};
1381 my $authpath = "/vms/$vmid";
1383 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1385 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1387 # Note: VNC ports are acessible from outside, so we do not gain any
1388 # security if we verify that $param->{port} belongs to VM $vmid. This
1389 # check is done by verifying the VNC ticket (inside VNC protocol).
1391 my $port = $param->{port
};
1393 return { port
=> $port };
1396 __PACKAGE__-
>register_method({
1397 name
=> 'spiceproxy',
1398 path
=> '{vmid}/spiceproxy',
1403 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1405 description
=> "Returns a SPICE configuration to connect to the VM.",
1407 additionalProperties
=> 0,
1409 node
=> get_standard_option
('pve-node'),
1410 vmid
=> get_standard_option
('pve-vmid'),
1411 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1414 returns
=> get_standard_option
('remote-viewer-config'),
1418 my $rpcenv = PVE
::RPCEnvironment
::get
();
1420 my $authuser = $rpcenv->get_user();
1422 my $vmid = $param->{vmid
};
1423 my $node = $param->{node
};
1424 my $proxy = $param->{proxy
};
1426 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1427 my $title = "VM $vmid";
1428 $title .= " - ". $conf->{name
} if $conf->{name
};
1430 my $port = PVE
::QemuServer
::spice_port
($vmid);
1432 my ($ticket, undef, $remote_viewer_config) =
1433 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1435 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1436 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1438 return $remote_viewer_config;
1441 __PACKAGE__-
>register_method({
1443 path
=> '{vmid}/status',
1446 description
=> "Directory index",
1451 additionalProperties
=> 0,
1453 node
=> get_standard_option
('pve-node'),
1454 vmid
=> get_standard_option
('pve-vmid'),
1462 subdir
=> { type
=> 'string' },
1465 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1471 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1474 { subdir
=> 'current' },
1475 { subdir
=> 'start' },
1476 { subdir
=> 'stop' },
1482 __PACKAGE__-
>register_method({
1483 name
=> 'vm_status',
1484 path
=> '{vmid}/status/current',
1487 protected
=> 1, # qemu pid files are only readable by root
1488 description
=> "Get virtual machine status.",
1490 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1493 additionalProperties
=> 0,
1495 node
=> get_standard_option
('pve-node'),
1496 vmid
=> get_standard_option
('pve-vmid'),
1499 returns
=> { type
=> 'object' },
1504 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1506 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1507 my $status = $vmstatus->{$param->{vmid
}};
1509 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1511 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1516 __PACKAGE__-
>register_method({
1518 path
=> '{vmid}/status/start',
1522 description
=> "Start virtual machine.",
1524 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1527 additionalProperties
=> 0,
1529 node
=> get_standard_option
('pve-node'),
1530 vmid
=> get_standard_option
('pve-vmid',
1531 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1532 skiplock
=> get_standard_option
('skiplock'),
1533 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1534 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1535 machine
=> get_standard_option
('pve-qm-machine'),
1544 my $rpcenv = PVE
::RPCEnvironment
::get
();
1546 my $authuser = $rpcenv->get_user();
1548 my $node = extract_param
($param, 'node');
1550 my $vmid = extract_param
($param, 'vmid');
1552 my $machine = extract_param
($param, 'machine');
1554 my $stateuri = extract_param
($param, 'stateuri');
1555 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1556 if $stateuri && $authuser ne 'root@pam';
1558 my $skiplock = extract_param
($param, 'skiplock');
1559 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1560 if $skiplock && $authuser ne 'root@pam';
1562 my $migratedfrom = extract_param
($param, 'migratedfrom');
1563 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1564 if $migratedfrom && $authuser ne 'root@pam';
1566 # read spice ticket from STDIN
1568 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1569 if (defined(my $line = <>)) {
1571 $spice_ticket = $line;
1575 PVE
::Cluster
::check_cfs_quorum
();
1577 my $storecfg = PVE
::Storage
::config
();
1579 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1580 $rpcenv->{type
} ne 'ha') {
1585 my $service = "vm:$vmid";
1587 my $cmd = ['ha-manager', 'enable', $service];
1589 print "Executing HA start for VM $vmid\n";
1591 PVE
::Tools
::run_command
($cmd);
1596 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1603 syslog
('info', "start VM $vmid: $upid\n");
1605 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1606 $machine, $spice_ticket);
1611 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1615 __PACKAGE__-
>register_method({
1617 path
=> '{vmid}/status/stop',
1621 description
=> "Stop virtual machine.",
1623 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1626 additionalProperties
=> 0,
1628 node
=> get_standard_option
('pve-node'),
1629 vmid
=> get_standard_option
('pve-vmid',
1630 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1631 skiplock
=> get_standard_option
('skiplock'),
1632 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1634 description
=> "Wait maximal timeout seconds.",
1640 description
=> "Do not decativate storage volumes.",
1653 my $rpcenv = PVE
::RPCEnvironment
::get
();
1655 my $authuser = $rpcenv->get_user();
1657 my $node = extract_param
($param, 'node');
1659 my $vmid = extract_param
($param, 'vmid');
1661 my $skiplock = extract_param
($param, 'skiplock');
1662 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1663 if $skiplock && $authuser ne 'root@pam';
1665 my $keepActive = extract_param
($param, 'keepActive');
1666 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1667 if $keepActive && $authuser ne 'root@pam';
1669 my $migratedfrom = extract_param
($param, 'migratedfrom');
1670 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1671 if $migratedfrom && $authuser ne 'root@pam';
1674 my $storecfg = PVE
::Storage
::config
();
1676 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1681 my $service = "vm:$vmid";
1683 my $cmd = ['ha-manager', 'disable', $service];
1685 print "Executing HA stop for VM $vmid\n";
1687 PVE
::Tools
::run_command
($cmd);
1692 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1698 syslog
('info', "stop VM $vmid: $upid\n");
1700 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1701 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1706 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1710 __PACKAGE__-
>register_method({
1712 path
=> '{vmid}/status/reset',
1716 description
=> "Reset virtual machine.",
1718 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1721 additionalProperties
=> 0,
1723 node
=> get_standard_option
('pve-node'),
1724 vmid
=> get_standard_option
('pve-vmid',
1725 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1726 skiplock
=> get_standard_option
('skiplock'),
1735 my $rpcenv = PVE
::RPCEnvironment
::get
();
1737 my $authuser = $rpcenv->get_user();
1739 my $node = extract_param
($param, 'node');
1741 my $vmid = extract_param
($param, 'vmid');
1743 my $skiplock = extract_param
($param, 'skiplock');
1744 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1745 if $skiplock && $authuser ne 'root@pam';
1747 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1752 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1757 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1760 __PACKAGE__-
>register_method({
1761 name
=> 'vm_shutdown',
1762 path
=> '{vmid}/status/shutdown',
1766 description
=> "Shutdown virtual machine.",
1768 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1771 additionalProperties
=> 0,
1773 node
=> get_standard_option
('pve-node'),
1774 vmid
=> get_standard_option
('pve-vmid',
1775 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1776 skiplock
=> get_standard_option
('skiplock'),
1778 description
=> "Wait maximal timeout seconds.",
1784 description
=> "Make sure the VM stops.",
1790 description
=> "Do not decativate storage volumes.",
1803 my $rpcenv = PVE
::RPCEnvironment
::get
();
1805 my $authuser = $rpcenv->get_user();
1807 my $node = extract_param
($param, 'node');
1809 my $vmid = extract_param
($param, 'vmid');
1811 my $skiplock = extract_param
($param, 'skiplock');
1812 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1813 if $skiplock && $authuser ne 'root@pam';
1815 my $keepActive = extract_param
($param, 'keepActive');
1816 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1817 if $keepActive && $authuser ne 'root@pam';
1819 my $storecfg = PVE
::Storage
::config
();
1824 syslog
('info', "shutdown VM $vmid: $upid\n");
1826 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1827 1, $param->{forceStop
}, $keepActive);
1832 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1835 __PACKAGE__-
>register_method({
1836 name
=> 'vm_suspend',
1837 path
=> '{vmid}/status/suspend',
1841 description
=> "Suspend virtual machine.",
1843 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1846 additionalProperties
=> 0,
1848 node
=> get_standard_option
('pve-node'),
1849 vmid
=> get_standard_option
('pve-vmid',
1850 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1851 skiplock
=> get_standard_option
('skiplock'),
1860 my $rpcenv = PVE
::RPCEnvironment
::get
();
1862 my $authuser = $rpcenv->get_user();
1864 my $node = extract_param
($param, 'node');
1866 my $vmid = extract_param
($param, 'vmid');
1868 my $skiplock = extract_param
($param, 'skiplock');
1869 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1870 if $skiplock && $authuser ne 'root@pam';
1872 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1877 syslog
('info', "suspend VM $vmid: $upid\n");
1879 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1884 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1887 __PACKAGE__-
>register_method({
1888 name
=> 'vm_resume',
1889 path
=> '{vmid}/status/resume',
1893 description
=> "Resume virtual machine.",
1895 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1898 additionalProperties
=> 0,
1900 node
=> get_standard_option
('pve-node'),
1901 vmid
=> get_standard_option
('pve-vmid',
1902 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1903 skiplock
=> get_standard_option
('skiplock'),
1904 nocheck
=> { type
=> 'boolean', optional
=> 1 },
1914 my $rpcenv = PVE
::RPCEnvironment
::get
();
1916 my $authuser = $rpcenv->get_user();
1918 my $node = extract_param
($param, 'node');
1920 my $vmid = extract_param
($param, 'vmid');
1922 my $skiplock = extract_param
($param, 'skiplock');
1923 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1924 if $skiplock && $authuser ne 'root@pam';
1926 my $nocheck = extract_param
($param, 'nocheck');
1928 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
1933 syslog
('info', "resume VM $vmid: $upid\n");
1935 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
1940 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1943 __PACKAGE__-
>register_method({
1944 name
=> 'vm_sendkey',
1945 path
=> '{vmid}/sendkey',
1949 description
=> "Send key event to virtual machine.",
1951 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1954 additionalProperties
=> 0,
1956 node
=> get_standard_option
('pve-node'),
1957 vmid
=> get_standard_option
('pve-vmid',
1958 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1959 skiplock
=> get_standard_option
('skiplock'),
1961 description
=> "The key (qemu monitor encoding).",
1966 returns
=> { type
=> 'null'},
1970 my $rpcenv = PVE
::RPCEnvironment
::get
();
1972 my $authuser = $rpcenv->get_user();
1974 my $node = extract_param
($param, 'node');
1976 my $vmid = extract_param
($param, 'vmid');
1978 my $skiplock = extract_param
($param, 'skiplock');
1979 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1980 if $skiplock && $authuser ne 'root@pam';
1982 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1987 __PACKAGE__-
>register_method({
1988 name
=> 'vm_feature',
1989 path
=> '{vmid}/feature',
1993 description
=> "Check if feature for virtual machine is available.",
1995 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1998 additionalProperties
=> 0,
2000 node
=> get_standard_option
('pve-node'),
2001 vmid
=> get_standard_option
('pve-vmid'),
2003 description
=> "Feature to check.",
2005 enum
=> [ 'snapshot', 'clone', 'copy' ],
2007 snapname
=> get_standard_option
('pve-snapshot-name', {
2015 hasFeature
=> { type
=> 'boolean' },
2018 items
=> { type
=> 'string' },
2025 my $node = extract_param
($param, 'node');
2027 my $vmid = extract_param
($param, 'vmid');
2029 my $snapname = extract_param
($param, 'snapname');
2031 my $feature = extract_param
($param, 'feature');
2033 my $running = PVE
::QemuServer
::check_running
($vmid);
2035 my $conf = PVE
::QemuServer
::load_config
($vmid);
2038 my $snap = $conf->{snapshots
}->{$snapname};
2039 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2042 my $storecfg = PVE
::Storage
::config
();
2044 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2045 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2048 hasFeature
=> $hasFeature,
2049 nodes
=> [ keys %$nodelist ],
2053 __PACKAGE__-
>register_method({
2055 path
=> '{vmid}/clone',
2059 description
=> "Create a copy of virtual machine/template.",
2061 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2062 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2063 "'Datastore.AllocateSpace' on any used storage.",
2066 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2068 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2069 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2074 additionalProperties
=> 0,
2076 node
=> get_standard_option
('pve-node'),
2077 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2078 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2081 type
=> 'string', format
=> 'dns-name',
2082 description
=> "Set a name for the new VM.",
2087 description
=> "Description for the new VM.",
2091 type
=> 'string', format
=> 'pve-poolid',
2092 description
=> "Add the new VM to the specified pool.",
2094 snapname
=> get_standard_option
('pve-snapshot-name', {
2097 storage
=> get_standard_option
('pve-storage-id', {
2098 description
=> "Target storage for full clone.",
2103 description
=> "Target format for file storage.",
2107 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2112 description
=> "Create a full copy of all disk. This is always done when " .
2113 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2116 target
=> get_standard_option
('pve-node', {
2117 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2128 my $rpcenv = PVE
::RPCEnvironment
::get
();
2130 my $authuser = $rpcenv->get_user();
2132 my $node = extract_param
($param, 'node');
2134 my $vmid = extract_param
($param, 'vmid');
2136 my $newid = extract_param
($param, 'newid');
2138 my $pool = extract_param
($param, 'pool');
2140 if (defined($pool)) {
2141 $rpcenv->check_pool_exist($pool);
2144 my $snapname = extract_param
($param, 'snapname');
2146 my $storage = extract_param
($param, 'storage');
2148 my $format = extract_param
($param, 'format');
2150 my $target = extract_param
($param, 'target');
2152 my $localnode = PVE
::INotify
::nodename
();
2154 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2156 PVE
::Cluster
::check_node_exists
($target) if $target;
2158 my $storecfg = PVE
::Storage
::config
();
2161 # check if storage is enabled on local node
2162 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2164 # check if storage is available on target node
2165 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2166 # clone only works if target storage is shared
2167 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2168 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2172 PVE
::Cluster
::check_cfs_quorum
();
2174 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2176 # exclusive lock if VM is running - else shared lock is enough;
2177 my $shared_lock = $running ?
0 : 1;
2181 # do all tests after lock
2182 # we also try to do all tests before we fork the worker
2184 my $conf = PVE
::QemuServer
::load_config
($vmid);
2186 PVE
::QemuServer
::check_lock
($conf);
2188 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2190 die "unexpected state change\n" if $verify_running != $running;
2192 die "snapshot '$snapname' does not exist\n"
2193 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2195 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2197 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2199 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2201 my $conffile = PVE
::QemuServer
::config_file
($newid);
2203 die "unable to create VM $newid: config file already exists\n"
2206 my $newconf = { lock => 'clone' };
2211 foreach my $opt (keys %$oldconf) {
2212 my $value = $oldconf->{$opt};
2214 # do not copy snapshot related info
2215 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2216 $opt eq 'vmstate' || $opt eq 'snapstate';
2218 # no need to copy unused images, because VMID(owner) changes anyways
2219 next if $opt =~ m/^unused\d+$/;
2221 # always change MAC! address
2222 if ($opt =~ m/^net(\d+)$/) {
2223 my $net = PVE
::QemuServer
::parse_net
($value);
2224 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2225 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2226 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2227 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2228 die "unable to parse drive options for '$opt'\n" if !$drive;
2229 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2230 $newconf->{$opt} = $value; # simply copy configuration
2232 if ($param->{full
}) {
2233 die "Full clone feature is not available"
2234 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2235 $fullclone->{$opt} = 1;
2237 # not full means clone instead of copy
2238 die "Linked clone feature is not available"
2239 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2241 $drives->{$opt} = $drive;
2242 push @$vollist, $drive->{file
};
2245 # copy everything else
2246 $newconf->{$opt} = $value;
2250 # auto generate a new uuid
2251 my ($uuid, $uuid_str);
2252 UUID
::generate
($uuid);
2253 UUID
::unparse
($uuid, $uuid_str);
2254 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2255 $smbios1->{uuid
} = $uuid_str;
2256 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2258 delete $newconf->{template
};
2260 if ($param->{name
}) {
2261 $newconf->{name
} = $param->{name
};
2263 if ($oldconf->{name
}) {
2264 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2266 $newconf->{name
} = "Copy-of-VM-$vmid";
2270 if ($param->{description
}) {
2271 $newconf->{description
} = $param->{description
};
2274 # create empty/temp config - this fails if VM already exists on other node
2275 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2280 my $newvollist = [];
2283 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2285 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2287 foreach my $opt (keys %$drives) {
2288 my $drive = $drives->{$opt};
2290 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2291 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2293 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2295 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2298 delete $newconf->{lock};
2299 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2302 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2303 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname);
2305 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2306 die "Failed to move config to node '$target' - rename failed: $!\n"
2307 if !rename($conffile, $newconffile);
2310 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2315 sleep 1; # some storage like rbd need to wait before release volume - really?
2317 foreach my $volid (@$newvollist) {
2318 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2321 die "clone failed: $err";
2327 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2329 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2332 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2333 # Aquire exclusive lock lock for $newid
2334 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2339 __PACKAGE__-
>register_method({
2340 name
=> 'move_vm_disk',
2341 path
=> '{vmid}/move_disk',
2345 description
=> "Move volume to different storage.",
2347 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2348 "and 'Datastore.AllocateSpace' permissions on the storage.",
2351 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2352 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2356 additionalProperties
=> 0,
2358 node
=> get_standard_option
('pve-node'),
2359 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2362 description
=> "The disk you want to move.",
2363 enum
=> [ PVE
::QemuServer
::disknames
() ],
2365 storage
=> get_standard_option
('pve-storage-id', {
2366 description
=> "Target storage.",
2367 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2371 description
=> "Target Format.",
2372 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2377 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2383 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2391 description
=> "the task ID.",
2396 my $rpcenv = PVE
::RPCEnvironment
::get
();
2398 my $authuser = $rpcenv->get_user();
2400 my $node = extract_param
($param, 'node');
2402 my $vmid = extract_param
($param, 'vmid');
2404 my $digest = extract_param
($param, 'digest');
2406 my $disk = extract_param
($param, 'disk');
2408 my $storeid = extract_param
($param, 'storage');
2410 my $format = extract_param
($param, 'format');
2412 my $storecfg = PVE
::Storage
::config
();
2414 my $updatefn = sub {
2416 my $conf = PVE
::QemuServer
::load_config
($vmid);
2418 die "checksum missmatch (file change by other user?)\n"
2419 if $digest && $digest ne $conf->{digest
};
2421 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2423 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2425 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2427 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2430 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2431 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2435 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2436 (!$format || !$oldfmt || $oldfmt eq $format);
2438 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2440 my $running = PVE
::QemuServer
::check_running
($vmid);
2442 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2446 my $newvollist = [];
2449 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2451 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2452 $vmid, $storeid, $format, 1, $newvollist);
2454 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2456 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2458 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2461 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2462 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2469 foreach my $volid (@$newvollist) {
2470 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2473 die "storage migration failed: $err";
2476 if ($param->{delete}) {
2477 if (PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, undef, $old_volid)) {
2478 warn "volume $old_volid still has snapshots, can't delete it\n";
2479 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2480 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2482 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2488 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2491 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2494 __PACKAGE__-
>register_method({
2495 name
=> 'migrate_vm',
2496 path
=> '{vmid}/migrate',
2500 description
=> "Migrate virtual machine. Creates a new migration task.",
2502 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2505 additionalProperties
=> 0,
2507 node
=> get_standard_option
('pve-node'),
2508 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2509 target
=> get_standard_option
('pve-node', {
2510 description
=> "Target node.",
2511 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2515 description
=> "Use online/live migration.",
2520 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2527 description
=> "the task ID.",
2532 my $rpcenv = PVE
::RPCEnvironment
::get
();
2534 my $authuser = $rpcenv->get_user();
2536 my $target = extract_param
($param, 'target');
2538 my $localnode = PVE
::INotify
::nodename
();
2539 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2541 PVE
::Cluster
::check_cfs_quorum
();
2543 PVE
::Cluster
::check_node_exists
($target);
2545 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2547 my $vmid = extract_param
($param, 'vmid');
2549 raise_param_exc
({ force
=> "Only root may use this option." })
2550 if $param->{force
} && $authuser ne 'root@pam';
2553 my $conf = PVE
::QemuServer
::load_config
($vmid);
2555 # try to detect errors early
2557 PVE
::QemuServer
::check_lock
($conf);
2559 if (PVE
::QemuServer
::check_running
($vmid)) {
2560 die "cant migrate running VM without --online\n"
2561 if !$param->{online
};
2564 my $storecfg = PVE
::Storage
::config
();
2565 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2567 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2572 my $service = "vm:$vmid";
2574 my $cmd = ['ha-manager', 'migrate', $service, $target];
2576 print "Executing HA migrate for VM $vmid to node $target\n";
2578 PVE
::Tools
::run_command
($cmd);
2583 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2590 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2593 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2598 __PACKAGE__-
>register_method({
2600 path
=> '{vmid}/monitor',
2604 description
=> "Execute Qemu monitor commands.",
2606 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2609 additionalProperties
=> 0,
2611 node
=> get_standard_option
('pve-node'),
2612 vmid
=> get_standard_option
('pve-vmid'),
2615 description
=> "The monitor command.",
2619 returns
=> { type
=> 'string'},
2623 my $vmid = $param->{vmid
};
2625 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2629 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2631 $res = "ERROR: $@" if $@;
2636 __PACKAGE__-
>register_method({
2637 name
=> 'resize_vm',
2638 path
=> '{vmid}/resize',
2642 description
=> "Extend volume size.",
2644 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2647 additionalProperties
=> 0,
2649 node
=> get_standard_option
('pve-node'),
2650 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2651 skiplock
=> get_standard_option
('skiplock'),
2654 description
=> "The disk you want to resize.",
2655 enum
=> [PVE
::QemuServer
::disknames
()],
2659 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2660 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.",
2664 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2670 returns
=> { type
=> 'null'},
2674 my $rpcenv = PVE
::RPCEnvironment
::get
();
2676 my $authuser = $rpcenv->get_user();
2678 my $node = extract_param
($param, 'node');
2680 my $vmid = extract_param
($param, 'vmid');
2682 my $digest = extract_param
($param, 'digest');
2684 my $disk = extract_param
($param, 'disk');
2686 my $sizestr = extract_param
($param, 'size');
2688 my $skiplock = extract_param
($param, 'skiplock');
2689 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2690 if $skiplock && $authuser ne 'root@pam';
2692 my $storecfg = PVE
::Storage
::config
();
2694 my $updatefn = sub {
2696 my $conf = PVE
::QemuServer
::load_config
($vmid);
2698 die "checksum missmatch (file change by other user?)\n"
2699 if $digest && $digest ne $conf->{digest
};
2700 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2702 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2704 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2706 my (undef, undef, undef, undef, undef, undef, $format) =
2707 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2709 die "can't resize volume: $disk if snapshot exists\n"
2710 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2712 my $volid = $drive->{file
};
2714 die "disk '$disk' has no associated volume\n" if !$volid;
2716 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2718 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2720 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2722 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2724 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2725 my ($ext, $newsize, $unit) = ($1, $2, $4);
2728 $newsize = $newsize * 1024;
2729 } elsif ($unit eq 'M') {
2730 $newsize = $newsize * 1024 * 1024;
2731 } elsif ($unit eq 'G') {
2732 $newsize = $newsize * 1024 * 1024 * 1024;
2733 } elsif ($unit eq 'T') {
2734 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2737 $newsize += $size if $ext;
2738 $newsize = int($newsize);
2740 die "unable to skrink disk size\n" if $newsize < $size;
2742 return if $size == $newsize;
2744 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2746 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2748 $drive->{size
} = $newsize;
2749 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2751 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2754 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2758 __PACKAGE__-
>register_method({
2759 name
=> 'snapshot_list',
2760 path
=> '{vmid}/snapshot',
2762 description
=> "List all snapshots.",
2764 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2767 protected
=> 1, # qemu pid files are only readable by root
2769 additionalProperties
=> 0,
2771 vmid
=> get_standard_option
('pve-vmid'),
2772 node
=> get_standard_option
('pve-node'),
2781 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2786 my $vmid = $param->{vmid
};
2788 my $conf = PVE
::QemuServer
::load_config
($vmid);
2789 my $snaphash = $conf->{snapshots
} || {};
2793 foreach my $name (keys %$snaphash) {
2794 my $d = $snaphash->{$name};
2797 snaptime
=> $d->{snaptime
} || 0,
2798 vmstate
=> $d->{vmstate
} ?
1 : 0,
2799 description
=> $d->{description
} || '',
2801 $item->{parent
} = $d->{parent
} if $d->{parent
};
2802 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2806 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2807 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2808 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2810 push @$res, $current;
2815 __PACKAGE__-
>register_method({
2817 path
=> '{vmid}/snapshot',
2821 description
=> "Snapshot a VM.",
2823 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2826 additionalProperties
=> 0,
2828 node
=> get_standard_option
('pve-node'),
2829 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2830 snapname
=> get_standard_option
('pve-snapshot-name'),
2834 description
=> "Save the vmstate",
2839 description
=> "A textual description or comment.",
2845 description
=> "the task ID.",
2850 my $rpcenv = PVE
::RPCEnvironment
::get
();
2852 my $authuser = $rpcenv->get_user();
2854 my $node = extract_param
($param, 'node');
2856 my $vmid = extract_param
($param, 'vmid');
2858 my $snapname = extract_param
($param, 'snapname');
2860 die "unable to use snapshot name 'current' (reserved name)\n"
2861 if $snapname eq 'current';
2864 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2865 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2866 $param->{description
});
2869 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2872 __PACKAGE__-
>register_method({
2873 name
=> 'snapshot_cmd_idx',
2874 path
=> '{vmid}/snapshot/{snapname}',
2881 additionalProperties
=> 0,
2883 vmid
=> get_standard_option
('pve-vmid'),
2884 node
=> get_standard_option
('pve-node'),
2885 snapname
=> get_standard_option
('pve-snapshot-name'),
2894 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2901 push @$res, { cmd
=> 'rollback' };
2902 push @$res, { cmd
=> 'config' };
2907 __PACKAGE__-
>register_method({
2908 name
=> 'update_snapshot_config',
2909 path
=> '{vmid}/snapshot/{snapname}/config',
2913 description
=> "Update snapshot metadata.",
2915 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2918 additionalProperties
=> 0,
2920 node
=> get_standard_option
('pve-node'),
2921 vmid
=> get_standard_option
('pve-vmid'),
2922 snapname
=> get_standard_option
('pve-snapshot-name'),
2926 description
=> "A textual description or comment.",
2930 returns
=> { type
=> 'null' },
2934 my $rpcenv = PVE
::RPCEnvironment
::get
();
2936 my $authuser = $rpcenv->get_user();
2938 my $vmid = extract_param
($param, 'vmid');
2940 my $snapname = extract_param
($param, 'snapname');
2942 return undef if !defined($param->{description
});
2944 my $updatefn = sub {
2946 my $conf = PVE
::QemuServer
::load_config
($vmid);
2948 PVE
::QemuServer
::check_lock
($conf);
2950 my $snap = $conf->{snapshots
}->{$snapname};
2952 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2954 $snap->{description
} = $param->{description
} if defined($param->{description
});
2956 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2959 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2964 __PACKAGE__-
>register_method({
2965 name
=> 'get_snapshot_config',
2966 path
=> '{vmid}/snapshot/{snapname}/config',
2969 description
=> "Get snapshot configuration",
2971 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2974 additionalProperties
=> 0,
2976 node
=> get_standard_option
('pve-node'),
2977 vmid
=> get_standard_option
('pve-vmid'),
2978 snapname
=> get_standard_option
('pve-snapshot-name'),
2981 returns
=> { type
=> "object" },
2985 my $rpcenv = PVE
::RPCEnvironment
::get
();
2987 my $authuser = $rpcenv->get_user();
2989 my $vmid = extract_param
($param, 'vmid');
2991 my $snapname = extract_param
($param, 'snapname');
2993 my $conf = PVE
::QemuServer
::load_config
($vmid);
2995 my $snap = $conf->{snapshots
}->{$snapname};
2997 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3002 __PACKAGE__-
>register_method({
3004 path
=> '{vmid}/snapshot/{snapname}/rollback',
3008 description
=> "Rollback VM state to specified snapshot.",
3010 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3013 additionalProperties
=> 0,
3015 node
=> get_standard_option
('pve-node'),
3016 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3017 snapname
=> get_standard_option
('pve-snapshot-name'),
3022 description
=> "the task ID.",
3027 my $rpcenv = PVE
::RPCEnvironment
::get
();
3029 my $authuser = $rpcenv->get_user();
3031 my $node = extract_param
($param, 'node');
3033 my $vmid = extract_param
($param, 'vmid');
3035 my $snapname = extract_param
($param, 'snapname');
3038 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3039 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3042 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3045 __PACKAGE__-
>register_method({
3046 name
=> 'delsnapshot',
3047 path
=> '{vmid}/snapshot/{snapname}',
3051 description
=> "Delete a VM snapshot.",
3053 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3056 additionalProperties
=> 0,
3058 node
=> get_standard_option
('pve-node'),
3059 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3060 snapname
=> get_standard_option
('pve-snapshot-name'),
3064 description
=> "For removal from config file, even if removing disk snapshots fails.",
3070 description
=> "the task ID.",
3075 my $rpcenv = PVE
::RPCEnvironment
::get
();
3077 my $authuser = $rpcenv->get_user();
3079 my $node = extract_param
($param, 'node');
3081 my $vmid = extract_param
($param, 'vmid');
3083 my $snapname = extract_param
($param, 'snapname');
3086 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3087 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3090 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3093 __PACKAGE__-
>register_method({
3095 path
=> '{vmid}/template',
3099 description
=> "Create a Template.",
3101 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3102 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3105 additionalProperties
=> 0,
3107 node
=> get_standard_option
('pve-node'),
3108 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3112 description
=> "If you want to convert only 1 disk to base image.",
3113 enum
=> [PVE
::QemuServer
::disknames
()],
3118 returns
=> { type
=> 'null'},
3122 my $rpcenv = PVE
::RPCEnvironment
::get
();
3124 my $authuser = $rpcenv->get_user();
3126 my $node = extract_param
($param, 'node');
3128 my $vmid = extract_param
($param, 'vmid');
3130 my $disk = extract_param
($param, 'disk');
3132 my $updatefn = sub {
3134 my $conf = PVE
::QemuServer
::load_config
($vmid);
3136 PVE
::QemuServer
::check_lock
($conf);
3138 die "unable to create template, because VM contains snapshots\n"
3139 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3141 die "you can't convert a template to a template\n"
3142 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3144 die "you can't convert a VM to template if VM is running\n"
3145 if PVE
::QemuServer
::check_running
($vmid);
3148 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3151 $conf->{template
} = 1;
3152 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3154 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3157 PVE
::QemuServer
::lock_config
($vmid, $updatefn);