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);
19 use PVE
::RPCEnvironment
;
20 use PVE
::AccessControl
;
24 use PVE
::API2
::Firewall
::VM
;
25 use PVE
::HA
::Env
::PVE2
;
28 use Data
::Dumper
; # fixme: remove
30 use base
qw(PVE::RESTHandler);
32 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.";
34 my $resolve_cdrom_alias = sub {
37 if (my $value = $param->{cdrom
}) {
38 $value .= ",media=cdrom" if $value !~ m/media=/;
39 $param->{ide2
} = $value;
40 delete $param->{cdrom
};
44 my $check_storage_access = sub {
45 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
47 PVE
::QemuServer
::foreach_drive
($settings, sub {
48 my ($ds, $drive) = @_;
50 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
52 my $volid = $drive->{file
};
54 if (!$volid || $volid eq 'none') {
56 } elsif ($isCDROM && ($volid eq 'cdrom')) {
57 $rpcenv->check($authuser, "/", ['Sys.Console']);
58 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
59 my ($storeid, $size) = ($2 || $default_storage, $3);
60 die "no storage ID specified (and no default storage)\n" if !$storeid;
61 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
63 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
68 my $check_storage_access_clone = sub {
69 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
73 PVE
::QemuServer
::foreach_drive
($conf, sub {
74 my ($ds, $drive) = @_;
76 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
78 my $volid = $drive->{file
};
80 return if !$volid || $volid eq 'none';
83 if ($volid eq 'cdrom') {
84 $rpcenv->check($authuser, "/", ['Sys.Console']);
86 # we simply allow access
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
93 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
94 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
95 $sharedvm = 0 if !$scfg->{shared
};
97 $sid = $storage if $storage;
98 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
105 # Note: $pool is only needed when creating a VM, because pool permissions
106 # are automatically inherited if VM already exists inside a pool.
107 my $create_disks = sub {
108 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
113 PVE
::QemuServer
::foreach_drive
($settings, sub {
114 my ($ds, $disk) = @_;
116 my $volid = $disk->{file
};
118 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
119 delete $disk->{size
};
120 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
121 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
122 my ($storeid, $size) = ($2 || $default_storage, $3);
123 die "no storage ID specified (and no default storage)\n" if !$storeid;
124 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
125 my $fmt = $disk->{format
} || $defformat;
128 if ($ds eq 'efidisk0') {
130 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
131 die "uefi vars image not found\n" if ! -f
$ovmfvars;
132 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
134 $disk->{file
} = $volid;
135 $disk->{size
} = 128*1024;
136 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
137 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
138 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
139 my $path = PVE
::Storage
::path
($storecfg, $volid);
140 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
141 push @$efidiskcmd, $ovmfvars;
142 push @$efidiskcmd, $path;
144 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
146 eval { PVE
::Tools
::run_command
($efidiskcmd); };
148 die "Copying of EFI Vars image failed: $err" if $err;
150 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
151 $fmt, undef, $size*1024*1024);
152 $disk->{file
} = $volid;
153 $disk->{size
} = $size*1024*1024*1024;
155 push @$vollist, $volid;
156 delete $disk->{format
}; # no longer needed
157 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
160 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
162 my $volid_is_new = 1;
165 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
166 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
171 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
173 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
175 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
177 die "volume $volid does not exists\n" if !$size;
179 $disk->{size
} = $size;
182 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
186 # free allocated images on error
188 syslog
('err', "VM $vmid creating disks failed");
189 foreach my $volid (@$vollist) {
190 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
196 # modify vm config if everything went well
197 foreach my $ds (keys %$res) {
198 $conf->{$ds} = $res->{$ds};
215 my $memoryoptions = {
221 my $hwtypeoptions = {
233 my $generaloptions = {
240 'migrate_downtime' => 1,
241 'migrate_speed' => 1,
253 my $vmpoweroptions = {
262 my $check_vm_modify_config_perm = sub {
263 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
265 return 1 if $authuser eq 'root@pam';
267 foreach my $opt (@$key_list) {
268 # disk checks need to be done somewhere else
269 next if PVE
::QemuServer
::is_valid_drivename
($opt);
270 next if $opt eq 'cdrom';
271 next if $opt =~ m/^unused\d+$/;
273 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
274 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
275 } elsif ($memoryoptions->{$opt}) {
276 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
277 } elsif ($hwtypeoptions->{$opt}) {
278 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
279 } elsif ($generaloptions->{$opt}) {
280 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
281 # special case for startup since it changes host behaviour
282 if ($opt eq 'startup') {
283 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
285 } elsif ($vmpoweroptions->{$opt}) {
286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
287 } elsif ($diskoptions->{$opt}) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
289 } elsif ($opt =~ m/^net\d+$/) {
290 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
292 # catches usb\d+, hostpci\d+, args, lock, etc.
293 # new options will be checked here
294 die "only root can set '$opt' config\n";
301 __PACKAGE__-
>register_method({
305 description
=> "Virtual machine index (per node).",
307 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
311 protected
=> 1, # qemu pid files are only readable by root
313 additionalProperties
=> 0,
315 node
=> get_standard_option
('pve-node'),
319 description
=> "Determine the full status of active VMs.",
329 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
334 my $rpcenv = PVE
::RPCEnvironment
::get
();
335 my $authuser = $rpcenv->get_user();
337 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
340 foreach my $vmid (keys %$vmstatus) {
341 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
343 my $data = $vmstatus->{$vmid};
344 $data->{vmid
} = int($vmid);
353 __PACKAGE__-
>register_method({
357 description
=> "Create or restore a virtual machine.",
359 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
360 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
361 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
362 user
=> 'all', # check inside
367 additionalProperties
=> 0,
368 properties
=> PVE
::QemuServer
::json_config_properties
(
370 node
=> get_standard_option
('pve-node'),
371 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
373 description
=> "The backup file.",
377 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
379 storage
=> get_standard_option
('pve-storage-id', {
380 description
=> "Default storage.",
382 completion
=> \
&PVE
::QemuServer
::complete_storage
,
387 description
=> "Allow to overwrite existing VM.",
388 requires
=> 'archive',
393 description
=> "Assign a unique random ethernet address.",
394 requires
=> 'archive',
398 type
=> 'string', format
=> 'pve-poolid',
399 description
=> "Add the VM to the specified pool.",
409 my $rpcenv = PVE
::RPCEnvironment
::get
();
411 my $authuser = $rpcenv->get_user();
413 my $node = extract_param
($param, 'node');
415 my $vmid = extract_param
($param, 'vmid');
417 my $archive = extract_param
($param, 'archive');
419 my $storage = extract_param
($param, 'storage');
421 my $force = extract_param
($param, 'force');
423 my $unique = extract_param
($param, 'unique');
425 my $pool = extract_param
($param, 'pool');
427 my $filename = PVE
::QemuConfig-
>config_file($vmid);
429 my $storecfg = PVE
::Storage
::config
();
431 PVE
::Cluster
::check_cfs_quorum
();
433 if (defined($pool)) {
434 $rpcenv->check_pool_exist($pool);
437 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
438 if defined($storage);
440 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
442 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
444 } elsif ($archive && $force && (-f
$filename) &&
445 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
446 # OK: user has VM.Backup permissions, and want to restore an existing VM
452 &$resolve_cdrom_alias($param);
454 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
456 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
458 foreach my $opt (keys %$param) {
459 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
460 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
461 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
463 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
464 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
468 PVE
::QemuServer
::add_random_macs
($param);
470 my $keystr = join(' ', keys %$param);
471 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
473 if ($archive eq '-') {
474 die "pipe requires cli environment\n"
475 if $rpcenv->{type
} ne 'cli';
477 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
478 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
482 my $restorefn = sub {
483 my $vmlist = PVE
::Cluster
::get_vmlist
();
484 if ($vmlist->{ids
}->{$vmid}) {
485 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
486 if ($current_node eq $node) {
487 my $conf = PVE
::QemuConfig-
>load_config($vmid);
489 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
491 die "unable to restore vm $vmid - config file already exists\n"
494 die "unable to restore vm $vmid - vm is running\n"
495 if PVE
::QemuServer
::check_running
($vmid);
497 die "unable to restore vm $vmid - vm is a template\n"
498 if PVE
::QemuConfig-
>is_template($conf);
501 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
506 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
509 unique
=> $unique });
511 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
514 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
520 PVE
::Cluster
::check_vmid_unused
($vmid);
530 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
532 # try to be smart about bootdisk
533 my @disks = PVE
::QemuServer
::valid_drive_names
();
535 foreach my $ds (reverse @disks) {
536 next if !$conf->{$ds};
537 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
538 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
542 if (!$conf->{bootdisk
} && $firstdisk) {
543 $conf->{bootdisk
} = $firstdisk;
546 # auto generate uuid if user did not specify smbios1 option
547 if (!$conf->{smbios1
}) {
548 my ($uuid, $uuid_str);
549 UUID
::generate
($uuid);
550 UUID
::unparse
($uuid, $uuid_str);
551 $conf->{smbios1
} = "uuid=$uuid_str";
554 PVE
::QemuConfig-
>write_config($vmid, $conf);
560 foreach my $volid (@$vollist) {
561 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
564 die "create failed - $err";
567 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
570 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
573 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
576 __PACKAGE__-
>register_method({
581 description
=> "Directory index",
586 additionalProperties
=> 0,
588 node
=> get_standard_option
('pve-node'),
589 vmid
=> get_standard_option
('pve-vmid'),
597 subdir
=> { type
=> 'string' },
600 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
606 { subdir
=> 'config' },
607 { subdir
=> 'pending' },
608 { subdir
=> 'status' },
609 { subdir
=> 'unlink' },
610 { subdir
=> 'vncproxy' },
611 { subdir
=> 'migrate' },
612 { subdir
=> 'resize' },
613 { subdir
=> 'move' },
615 { subdir
=> 'rrddata' },
616 { subdir
=> 'monitor' },
617 { subdir
=> 'agent' },
618 { subdir
=> 'snapshot' },
619 { subdir
=> 'spiceproxy' },
620 { subdir
=> 'sendkey' },
621 { subdir
=> 'firewall' },
627 __PACKAGE__-
>register_method ({
628 subclass
=> "PVE::API2::Firewall::VM",
629 path
=> '{vmid}/firewall',
632 __PACKAGE__-
>register_method({
634 path
=> '{vmid}/rrd',
636 protected
=> 1, # fixme: can we avoid that?
638 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
640 description
=> "Read VM RRD statistics (returns PNG)",
642 additionalProperties
=> 0,
644 node
=> get_standard_option
('pve-node'),
645 vmid
=> get_standard_option
('pve-vmid'),
647 description
=> "Specify the time frame you are interested in.",
649 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
652 description
=> "The list of datasources you want to display.",
653 type
=> 'string', format
=> 'pve-configid-list',
656 description
=> "The RRD consolidation function",
658 enum
=> [ 'AVERAGE', 'MAX' ],
666 filename
=> { type
=> 'string' },
672 return PVE
::Cluster
::create_rrd_graph
(
673 "pve2-vm/$param->{vmid}", $param->{timeframe
},
674 $param->{ds
}, $param->{cf
});
678 __PACKAGE__-
>register_method({
680 path
=> '{vmid}/rrddata',
682 protected
=> 1, # fixme: can we avoid that?
684 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
686 description
=> "Read VM RRD statistics",
688 additionalProperties
=> 0,
690 node
=> get_standard_option
('pve-node'),
691 vmid
=> get_standard_option
('pve-vmid'),
693 description
=> "Specify the time frame you are interested in.",
695 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
698 description
=> "The RRD consolidation function",
700 enum
=> [ 'AVERAGE', 'MAX' ],
715 return PVE
::Cluster
::create_rrd_data
(
716 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
720 __PACKAGE__-
>register_method({
722 path
=> '{vmid}/config',
725 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
727 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
730 additionalProperties
=> 0,
732 node
=> get_standard_option
('pve-node'),
733 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
735 description
=> "Get current values (instead of pending values).",
747 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
754 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
756 delete $conf->{snapshots
};
758 if (!$param->{current
}) {
759 foreach my $opt (keys %{$conf->{pending
}}) {
760 next if $opt eq 'delete';
761 my $value = $conf->{pending
}->{$opt};
762 next if ref($value); # just to be sure
763 $conf->{$opt} = $value;
765 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
766 foreach my $opt (keys %$pending_delete_hash) {
767 delete $conf->{$opt} if $conf->{$opt};
771 delete $conf->{pending
};
776 __PACKAGE__-
>register_method({
777 name
=> 'vm_pending',
778 path
=> '{vmid}/pending',
781 description
=> "Get virtual machine configuration, including pending changes.",
783 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
786 additionalProperties
=> 0,
788 node
=> get_standard_option
('pve-node'),
789 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
798 description
=> "Configuration option name.",
802 description
=> "Current value.",
807 description
=> "Pending value.",
812 description
=> "Indicates a pending delete request if present and not 0. " .
813 "The value 2 indicates a force-delete request.",
825 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
827 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
831 foreach my $opt (keys %$conf) {
832 next if ref($conf->{$opt});
833 my $item = { key
=> $opt };
834 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
835 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
836 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
840 foreach my $opt (keys %{$conf->{pending
}}) {
841 next if $opt eq 'delete';
842 next if ref($conf->{pending
}->{$opt}); # just to be sure
843 next if defined($conf->{$opt});
844 my $item = { key
=> $opt };
845 $item->{pending
} = $conf->{pending
}->{$opt};
849 while (my ($opt, $force) = each %$pending_delete_hash) {
850 next if $conf->{pending
}->{$opt}; # just to be sure
851 next if $conf->{$opt};
852 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
859 # POST/PUT {vmid}/config implementation
861 # The original API used PUT (idempotent) an we assumed that all operations
862 # are fast. But it turned out that almost any configuration change can
863 # involve hot-plug actions, or disk alloc/free. Such actions can take long
864 # time to complete and have side effects (not idempotent).
866 # The new implementation uses POST and forks a worker process. We added
867 # a new option 'background_delay'. If specified we wait up to
868 # 'background_delay' second for the worker task to complete. It returns null
869 # if the task is finished within that time, else we return the UPID.
871 my $update_vm_api = sub {
872 my ($param, $sync) = @_;
874 my $rpcenv = PVE
::RPCEnvironment
::get
();
876 my $authuser = $rpcenv->get_user();
878 my $node = extract_param
($param, 'node');
880 my $vmid = extract_param
($param, 'vmid');
882 my $digest = extract_param
($param, 'digest');
884 my $background_delay = extract_param
($param, 'background_delay');
886 my @paramarr = (); # used for log message
887 foreach my $key (keys %$param) {
888 push @paramarr, "-$key", $param->{$key};
891 my $skiplock = extract_param
($param, 'skiplock');
892 raise_param_exc
({ skiplock
=> "Only root may use this option." })
893 if $skiplock && $authuser ne 'root@pam';
895 my $delete_str = extract_param
($param, 'delete');
897 my $revert_str = extract_param
($param, 'revert');
899 my $force = extract_param
($param, 'force');
901 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
903 my $storecfg = PVE
::Storage
::config
();
905 my $defaults = PVE
::QemuServer
::load_defaults
();
907 &$resolve_cdrom_alias($param);
909 # now try to verify all parameters
912 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
913 if (!PVE
::QemuServer
::option_exists
($opt)) {
914 raise_param_exc
({ revert
=> "unknown option '$opt'" });
917 raise_param_exc
({ delete => "you can't use '-$opt' and " .
918 "-revert $opt' at the same time" })
919 if defined($param->{$opt});
925 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
926 $opt = 'ide2' if $opt eq 'cdrom';
928 raise_param_exc
({ delete => "you can't use '-$opt' and " .
929 "-delete $opt' at the same time" })
930 if defined($param->{$opt});
932 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
933 "-revert $opt' at the same time" })
936 if (!PVE
::QemuServer
::option_exists
($opt)) {
937 raise_param_exc
({ delete => "unknown option '$opt'" });
943 foreach my $opt (keys %$param) {
944 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
946 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
947 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
948 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
949 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
950 } elsif ($opt =~ m/^net(\d+)$/) {
952 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
953 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
957 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
959 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
961 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
965 my $conf = PVE
::QemuConfig-
>load_config($vmid);
967 die "checksum missmatch (file change by other user?)\n"
968 if $digest && $digest ne $conf->{digest
};
970 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
972 foreach my $opt (keys %$revert) {
973 if (defined($conf->{$opt})) {
974 $param->{$opt} = $conf->{$opt};
975 } elsif (defined($conf->{pending
}->{$opt})) {
980 if ($param->{memory
} || defined($param->{balloon
})) {
981 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
982 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
984 die "balloon value too large (must be smaller than assigned memory)\n"
985 if $balloon && $balloon > $maxmem;
988 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
992 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
994 # write updates to pending section
996 my $modified = {}; # record what $option we modify
998 foreach my $opt (@delete) {
999 $modified->{$opt} = 1;
1000 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1001 if ($opt =~ m/^unused/) {
1002 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1003 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1004 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1005 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1006 delete $conf->{$opt};
1007 PVE
::QemuConfig-
>write_config($vmid, $conf);
1009 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1010 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1011 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1012 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1013 if defined($conf->{pending
}->{$opt});
1014 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1015 PVE
::QemuConfig-
>write_config($vmid, $conf);
1017 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1018 PVE
::QemuConfig-
>write_config($vmid, $conf);
1022 foreach my $opt (keys %$param) { # add/change
1023 $modified->{$opt} = 1;
1024 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1025 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1027 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1028 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1029 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1030 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1032 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1034 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1035 if defined($conf->{pending
}->{$opt});
1037 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1039 $conf->{pending
}->{$opt} = $param->{$opt};
1041 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1042 PVE
::QemuConfig-
>write_config($vmid, $conf);
1045 # remove pending changes when nothing changed
1046 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1047 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1048 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1050 return if !scalar(keys %{$conf->{pending
}});
1052 my $running = PVE
::QemuServer
::check_running
($vmid);
1054 # apply pending changes
1056 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1060 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1061 raise_param_exc
($errors) if scalar(keys %$errors);
1063 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1073 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1075 if ($background_delay) {
1077 # Note: It would be better to do that in the Event based HTTPServer
1078 # to avoid blocking call to sleep.
1080 my $end_time = time() + $background_delay;
1082 my $task = PVE
::Tools
::upid_decode
($upid);
1085 while (time() < $end_time) {
1086 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1088 sleep(1); # this gets interrupted when child process ends
1092 my $status = PVE
::Tools
::upid_read_status
($upid);
1093 return undef if $status eq 'OK';
1102 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1105 my $vm_config_perm_list = [
1110 'VM.Config.Network',
1112 'VM.Config.Options',
1115 __PACKAGE__-
>register_method({
1116 name
=> 'update_vm_async',
1117 path
=> '{vmid}/config',
1121 description
=> "Set virtual machine options (asynchrounous API).",
1123 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1126 additionalProperties
=> 0,
1127 properties
=> PVE
::QemuServer
::json_config_properties
(
1129 node
=> get_standard_option
('pve-node'),
1130 vmid
=> get_standard_option
('pve-vmid'),
1131 skiplock
=> get_standard_option
('skiplock'),
1133 type
=> 'string', format
=> 'pve-configid-list',
1134 description
=> "A list of settings you want to delete.",
1138 type
=> 'string', format
=> 'pve-configid-list',
1139 description
=> "Revert a pending change.",
1144 description
=> $opt_force_description,
1146 requires
=> 'delete',
1150 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1154 background_delay
=> {
1156 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1167 code
=> $update_vm_api,
1170 __PACKAGE__-
>register_method({
1171 name
=> 'update_vm',
1172 path
=> '{vmid}/config',
1176 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1178 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1181 additionalProperties
=> 0,
1182 properties
=> PVE
::QemuServer
::json_config_properties
(
1184 node
=> get_standard_option
('pve-node'),
1185 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1186 skiplock
=> get_standard_option
('skiplock'),
1188 type
=> 'string', format
=> 'pve-configid-list',
1189 description
=> "A list of settings you want to delete.",
1193 type
=> 'string', format
=> 'pve-configid-list',
1194 description
=> "Revert a pending change.",
1199 description
=> $opt_force_description,
1201 requires
=> 'delete',
1205 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1211 returns
=> { type
=> 'null' },
1214 &$update_vm_api($param, 1);
1220 __PACKAGE__-
>register_method({
1221 name
=> 'destroy_vm',
1226 description
=> "Destroy the vm (also delete all used/owned volumes).",
1228 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1231 additionalProperties
=> 0,
1233 node
=> get_standard_option
('pve-node'),
1234 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1235 skiplock
=> get_standard_option
('skiplock'),
1244 my $rpcenv = PVE
::RPCEnvironment
::get
();
1246 my $authuser = $rpcenv->get_user();
1248 my $vmid = $param->{vmid
};
1250 my $skiplock = $param->{skiplock
};
1251 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1252 if $skiplock && $authuser ne 'root@pam';
1255 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1257 my $storecfg = PVE
::Storage
::config
();
1259 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1261 die "unable to remove VM $vmid - used in HA resources\n"
1262 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1264 # early tests (repeat after locking)
1265 die "VM $vmid is running - destroy failed\n"
1266 if PVE
::QemuServer
::check_running
($vmid);
1271 syslog
('info', "destroy VM $vmid: $upid\n");
1273 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1275 PVE
::AccessControl
::remove_vm_access
($vmid);
1277 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1280 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1283 __PACKAGE__-
>register_method({
1285 path
=> '{vmid}/unlink',
1289 description
=> "Unlink/delete disk images.",
1291 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1294 additionalProperties
=> 0,
1296 node
=> get_standard_option
('pve-node'),
1297 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1299 type
=> 'string', format
=> 'pve-configid-list',
1300 description
=> "A list of disk IDs you want to delete.",
1304 description
=> $opt_force_description,
1309 returns
=> { type
=> 'null'},
1313 $param->{delete} = extract_param
($param, 'idlist');
1315 __PACKAGE__-
>update_vm($param);
1322 __PACKAGE__-
>register_method({
1324 path
=> '{vmid}/vncproxy',
1328 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1330 description
=> "Creates a TCP VNC proxy connections.",
1332 additionalProperties
=> 0,
1334 node
=> get_standard_option
('pve-node'),
1335 vmid
=> get_standard_option
('pve-vmid'),
1339 description
=> "starts websockify instead of vncproxy",
1344 additionalProperties
=> 0,
1346 user
=> { type
=> 'string' },
1347 ticket
=> { type
=> 'string' },
1348 cert
=> { type
=> 'string' },
1349 port
=> { type
=> 'integer' },
1350 upid
=> { type
=> 'string' },
1356 my $rpcenv = PVE
::RPCEnvironment
::get
();
1358 my $authuser = $rpcenv->get_user();
1360 my $vmid = $param->{vmid
};
1361 my $node = $param->{node
};
1362 my $websocket = $param->{websocket
};
1364 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1366 my $authpath = "/vms/$vmid";
1368 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1370 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1373 my ($remip, $family);
1376 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1377 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1378 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1379 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1381 $family = PVE
::Tools
::get_host_address_family
($node);
1384 my $port = PVE
::Tools
::next_vnc_port
($family);
1391 syslog
('info', "starting vnc proxy $upid\n");
1395 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1397 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1399 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1400 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1401 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1402 '-timeout', $timeout, '-authpath', $authpath,
1403 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1406 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1408 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1410 my $qmstr = join(' ', @$qmcmd);
1412 # also redirect stderr (else we get RFB protocol errors)
1413 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1416 PVE
::Tools
::run_command
($cmd);
1421 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1423 PVE
::Tools
::wait_for_vnc_port
($port);
1434 __PACKAGE__-
>register_method({
1435 name
=> 'vncwebsocket',
1436 path
=> '{vmid}/vncwebsocket',
1439 description
=> "You also need to pass a valid ticket (vncticket).",
1440 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1442 description
=> "Opens a weksocket for VNC traffic.",
1444 additionalProperties
=> 0,
1446 node
=> get_standard_option
('pve-node'),
1447 vmid
=> get_standard_option
('pve-vmid'),
1449 description
=> "Ticket from previous call to vncproxy.",
1454 description
=> "Port number returned by previous vncproxy call.",
1464 port
=> { type
=> 'string' },
1470 my $rpcenv = PVE
::RPCEnvironment
::get
();
1472 my $authuser = $rpcenv->get_user();
1474 my $vmid = $param->{vmid
};
1475 my $node = $param->{node
};
1477 my $authpath = "/vms/$vmid";
1479 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1481 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1483 # Note: VNC ports are acessible from outside, so we do not gain any
1484 # security if we verify that $param->{port} belongs to VM $vmid. This
1485 # check is done by verifying the VNC ticket (inside VNC protocol).
1487 my $port = $param->{port
};
1489 return { port
=> $port };
1492 __PACKAGE__-
>register_method({
1493 name
=> 'spiceproxy',
1494 path
=> '{vmid}/spiceproxy',
1499 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1501 description
=> "Returns a SPICE configuration to connect to the VM.",
1503 additionalProperties
=> 0,
1505 node
=> get_standard_option
('pve-node'),
1506 vmid
=> get_standard_option
('pve-vmid'),
1507 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1510 returns
=> get_standard_option
('remote-viewer-config'),
1514 my $rpcenv = PVE
::RPCEnvironment
::get
();
1516 my $authuser = $rpcenv->get_user();
1518 my $vmid = $param->{vmid
};
1519 my $node = $param->{node
};
1520 my $proxy = $param->{proxy
};
1522 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1523 my $title = "VM $vmid";
1524 $title .= " - ". $conf->{name
} if $conf->{name
};
1526 my $port = PVE
::QemuServer
::spice_port
($vmid);
1528 my ($ticket, undef, $remote_viewer_config) =
1529 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1531 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1532 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1534 return $remote_viewer_config;
1537 __PACKAGE__-
>register_method({
1539 path
=> '{vmid}/status',
1542 description
=> "Directory index",
1547 additionalProperties
=> 0,
1549 node
=> get_standard_option
('pve-node'),
1550 vmid
=> get_standard_option
('pve-vmid'),
1558 subdir
=> { type
=> 'string' },
1561 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1567 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1570 { subdir
=> 'current' },
1571 { subdir
=> 'start' },
1572 { subdir
=> 'stop' },
1578 __PACKAGE__-
>register_method({
1579 name
=> 'vm_status',
1580 path
=> '{vmid}/status/current',
1583 protected
=> 1, # qemu pid files are only readable by root
1584 description
=> "Get virtual machine status.",
1586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1589 additionalProperties
=> 0,
1591 node
=> get_standard_option
('pve-node'),
1592 vmid
=> get_standard_option
('pve-vmid'),
1595 returns
=> { type
=> 'object' },
1600 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1602 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1603 my $status = $vmstatus->{$param->{vmid
}};
1605 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1607 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1612 __PACKAGE__-
>register_method({
1614 path
=> '{vmid}/status/start',
1618 description
=> "Start 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_stopped
}),
1628 skiplock
=> get_standard_option
('skiplock'),
1629 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1630 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1633 enum
=> ['secure', 'insecure'],
1634 description
=> "Migration traffic is encrypted using an SSH " .
1635 "tunnel by default. On secure, completely private networks " .
1636 "this can be disabled to increase performance.",
1639 migration_network
=> {
1640 type
=> 'string', format
=> 'CIDR',
1641 description
=> "CIDR of the (sub) network that is used for migration.",
1644 machine
=> get_standard_option
('pve-qm-machine'),
1646 description
=> "Target migration storage . (1 = same storeid than original)",
1658 my $rpcenv = PVE
::RPCEnvironment
::get
();
1660 my $authuser = $rpcenv->get_user();
1662 my $node = extract_param
($param, 'node');
1664 my $vmid = extract_param
($param, 'vmid');
1666 my $machine = extract_param
($param, 'machine');
1668 my $stateuri = extract_param
($param, 'stateuri');
1669 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1670 if $stateuri && $authuser ne 'root@pam';
1672 my $skiplock = extract_param
($param, 'skiplock');
1673 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1674 if $skiplock && $authuser ne 'root@pam';
1676 my $migratedfrom = extract_param
($param, 'migratedfrom');
1677 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1678 if $migratedfrom && $authuser ne 'root@pam';
1680 my $migration_type = extract_param
($param, 'migration_type');
1681 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1682 if $migration_type && $authuser ne 'root@pam';
1684 my $migration_network = extract_param
($param, 'migration_network');
1685 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1686 if $migration_network && $authuser ne 'root@pam';
1688 my $targetstorage = extract_param
($param, 'targetstorage');
1689 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1690 if $targetstorage && $authuser ne 'root@pam';
1692 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1693 if $targetstorage && !$migratedfrom;
1695 # read spice ticket from STDIN
1697 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1698 if (defined(my $line = <>)) {
1700 $spice_ticket = $line;
1704 PVE
::Cluster
::check_cfs_quorum
();
1706 my $storecfg = PVE
::Storage
::config
();
1708 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1709 $rpcenv->{type
} ne 'ha') {
1714 my $service = "vm:$vmid";
1716 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1718 print "Executing HA start for VM $vmid\n";
1720 PVE
::Tools
::run_command
($cmd);
1725 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1732 syslog
('info', "start VM $vmid: $upid\n");
1734 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1735 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1740 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1744 __PACKAGE__-
>register_method({
1746 path
=> '{vmid}/status/stop',
1750 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1751 "is akin to pulling the power plug of a running computer and may damage the VM data",
1753 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1756 additionalProperties
=> 0,
1758 node
=> get_standard_option
('pve-node'),
1759 vmid
=> get_standard_option
('pve-vmid',
1760 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1761 skiplock
=> get_standard_option
('skiplock'),
1762 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1764 description
=> "Wait maximal timeout seconds.",
1770 description
=> "Do not deactivate storage volumes.",
1783 my $rpcenv = PVE
::RPCEnvironment
::get
();
1785 my $authuser = $rpcenv->get_user();
1787 my $node = extract_param
($param, 'node');
1789 my $vmid = extract_param
($param, 'vmid');
1791 my $skiplock = extract_param
($param, 'skiplock');
1792 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1793 if $skiplock && $authuser ne 'root@pam';
1795 my $keepActive = extract_param
($param, 'keepActive');
1796 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1797 if $keepActive && $authuser ne 'root@pam';
1799 my $migratedfrom = extract_param
($param, 'migratedfrom');
1800 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1801 if $migratedfrom && $authuser ne 'root@pam';
1804 my $storecfg = PVE
::Storage
::config
();
1806 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1811 my $service = "vm:$vmid";
1813 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1815 print "Executing HA stop for VM $vmid\n";
1817 PVE
::Tools
::run_command
($cmd);
1822 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1828 syslog
('info', "stop VM $vmid: $upid\n");
1830 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1831 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1836 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1840 __PACKAGE__-
>register_method({
1842 path
=> '{vmid}/status/reset',
1846 description
=> "Reset virtual machine.",
1848 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1851 additionalProperties
=> 0,
1853 node
=> get_standard_option
('pve-node'),
1854 vmid
=> get_standard_option
('pve-vmid',
1855 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1856 skiplock
=> get_standard_option
('skiplock'),
1865 my $rpcenv = PVE
::RPCEnvironment
::get
();
1867 my $authuser = $rpcenv->get_user();
1869 my $node = extract_param
($param, 'node');
1871 my $vmid = extract_param
($param, 'vmid');
1873 my $skiplock = extract_param
($param, 'skiplock');
1874 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1875 if $skiplock && $authuser ne 'root@pam';
1877 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1882 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1887 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1890 __PACKAGE__-
>register_method({
1891 name
=> 'vm_shutdown',
1892 path
=> '{vmid}/status/shutdown',
1896 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1897 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1899 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1902 additionalProperties
=> 0,
1904 node
=> get_standard_option
('pve-node'),
1905 vmid
=> get_standard_option
('pve-vmid',
1906 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1907 skiplock
=> get_standard_option
('skiplock'),
1909 description
=> "Wait maximal timeout seconds.",
1915 description
=> "Make sure the VM stops.",
1921 description
=> "Do not deactivate storage volumes.",
1934 my $rpcenv = PVE
::RPCEnvironment
::get
();
1936 my $authuser = $rpcenv->get_user();
1938 my $node = extract_param
($param, 'node');
1940 my $vmid = extract_param
($param, 'vmid');
1942 my $skiplock = extract_param
($param, 'skiplock');
1943 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1944 if $skiplock && $authuser ne 'root@pam';
1946 my $keepActive = extract_param
($param, 'keepActive');
1947 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1948 if $keepActive && $authuser ne 'root@pam';
1950 my $storecfg = PVE
::Storage
::config
();
1954 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1955 # otherwise, we will infer a shutdown command, but run into the timeout,
1956 # then when the vm is resumed, it will instantly shutdown
1958 # checking the qmp status here to get feedback to the gui/cli/api
1959 # and the status query should not take too long
1962 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1966 if (!$err && $qmpstatus->{status
} eq "paused") {
1967 if ($param->{forceStop
}) {
1968 warn "VM is paused - stop instead of shutdown\n";
1971 die "VM is paused - cannot shutdown\n";
1975 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
1976 ($rpcenv->{type
} ne 'ha')) {
1981 my $service = "vm:$vmid";
1983 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1985 print "Executing HA stop for VM $vmid\n";
1987 PVE
::Tools
::run_command
($cmd);
1992 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1999 syslog
('info', "shutdown VM $vmid: $upid\n");
2001 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2002 $shutdown, $param->{forceStop
}, $keepActive);
2007 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2011 __PACKAGE__-
>register_method({
2012 name
=> 'vm_suspend',
2013 path
=> '{vmid}/status/suspend',
2017 description
=> "Suspend virtual machine.",
2019 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2022 additionalProperties
=> 0,
2024 node
=> get_standard_option
('pve-node'),
2025 vmid
=> get_standard_option
('pve-vmid',
2026 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2027 skiplock
=> get_standard_option
('skiplock'),
2036 my $rpcenv = PVE
::RPCEnvironment
::get
();
2038 my $authuser = $rpcenv->get_user();
2040 my $node = extract_param
($param, 'node');
2042 my $vmid = extract_param
($param, 'vmid');
2044 my $skiplock = extract_param
($param, 'skiplock');
2045 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2046 if $skiplock && $authuser ne 'root@pam';
2048 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2053 syslog
('info', "suspend VM $vmid: $upid\n");
2055 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2060 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2063 __PACKAGE__-
>register_method({
2064 name
=> 'vm_resume',
2065 path
=> '{vmid}/status/resume',
2069 description
=> "Resume virtual machine.",
2071 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2074 additionalProperties
=> 0,
2076 node
=> get_standard_option
('pve-node'),
2077 vmid
=> get_standard_option
('pve-vmid',
2078 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2079 skiplock
=> get_standard_option
('skiplock'),
2080 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2090 my $rpcenv = PVE
::RPCEnvironment
::get
();
2092 my $authuser = $rpcenv->get_user();
2094 my $node = extract_param
($param, 'node');
2096 my $vmid = extract_param
($param, 'vmid');
2098 my $skiplock = extract_param
($param, 'skiplock');
2099 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2100 if $skiplock && $authuser ne 'root@pam';
2102 my $nocheck = extract_param
($param, 'nocheck');
2104 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2109 syslog
('info', "resume VM $vmid: $upid\n");
2111 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2116 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2119 __PACKAGE__-
>register_method({
2120 name
=> 'vm_sendkey',
2121 path
=> '{vmid}/sendkey',
2125 description
=> "Send key event to virtual machine.",
2127 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2130 additionalProperties
=> 0,
2132 node
=> get_standard_option
('pve-node'),
2133 vmid
=> get_standard_option
('pve-vmid',
2134 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2135 skiplock
=> get_standard_option
('skiplock'),
2137 description
=> "The key (qemu monitor encoding).",
2142 returns
=> { type
=> 'null'},
2146 my $rpcenv = PVE
::RPCEnvironment
::get
();
2148 my $authuser = $rpcenv->get_user();
2150 my $node = extract_param
($param, 'node');
2152 my $vmid = extract_param
($param, 'vmid');
2154 my $skiplock = extract_param
($param, 'skiplock');
2155 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2156 if $skiplock && $authuser ne 'root@pam';
2158 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2163 __PACKAGE__-
>register_method({
2164 name
=> 'vm_feature',
2165 path
=> '{vmid}/feature',
2169 description
=> "Check if feature for virtual machine is available.",
2171 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2174 additionalProperties
=> 0,
2176 node
=> get_standard_option
('pve-node'),
2177 vmid
=> get_standard_option
('pve-vmid'),
2179 description
=> "Feature to check.",
2181 enum
=> [ 'snapshot', 'clone', 'copy' ],
2183 snapname
=> get_standard_option
('pve-snapshot-name', {
2191 hasFeature
=> { type
=> 'boolean' },
2194 items
=> { type
=> 'string' },
2201 my $node = extract_param
($param, 'node');
2203 my $vmid = extract_param
($param, 'vmid');
2205 my $snapname = extract_param
($param, 'snapname');
2207 my $feature = extract_param
($param, 'feature');
2209 my $running = PVE
::QemuServer
::check_running
($vmid);
2211 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2214 my $snap = $conf->{snapshots
}->{$snapname};
2215 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2218 my $storecfg = PVE
::Storage
::config
();
2220 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2221 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2224 hasFeature
=> $hasFeature,
2225 nodes
=> [ keys %$nodelist ],
2229 __PACKAGE__-
>register_method({
2231 path
=> '{vmid}/clone',
2235 description
=> "Create a copy of virtual machine/template.",
2237 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2238 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2239 "'Datastore.AllocateSpace' on any used storage.",
2242 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2244 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2245 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2250 additionalProperties
=> 0,
2252 node
=> get_standard_option
('pve-node'),
2253 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2254 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2257 type
=> 'string', format
=> 'dns-name',
2258 description
=> "Set a name for the new VM.",
2263 description
=> "Description for the new VM.",
2267 type
=> 'string', format
=> 'pve-poolid',
2268 description
=> "Add the new VM to the specified pool.",
2270 snapname
=> get_standard_option
('pve-snapshot-name', {
2273 storage
=> get_standard_option
('pve-storage-id', {
2274 description
=> "Target storage for full clone.",
2279 description
=> "Target format for file storage.",
2283 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2288 description
=> "Create a full copy of all disk. This is always done when " .
2289 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2292 target
=> get_standard_option
('pve-node', {
2293 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2304 my $rpcenv = PVE
::RPCEnvironment
::get
();
2306 my $authuser = $rpcenv->get_user();
2308 my $node = extract_param
($param, 'node');
2310 my $vmid = extract_param
($param, 'vmid');
2312 my $newid = extract_param
($param, 'newid');
2314 my $pool = extract_param
($param, 'pool');
2316 if (defined($pool)) {
2317 $rpcenv->check_pool_exist($pool);
2320 my $snapname = extract_param
($param, 'snapname');
2322 my $storage = extract_param
($param, 'storage');
2324 my $format = extract_param
($param, 'format');
2326 my $target = extract_param
($param, 'target');
2328 my $localnode = PVE
::INotify
::nodename
();
2330 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2332 PVE
::Cluster
::check_node_exists
($target) if $target;
2334 my $storecfg = PVE
::Storage
::config
();
2337 # check if storage is enabled on local node
2338 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2340 # check if storage is available on target node
2341 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2342 # clone only works if target storage is shared
2343 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2344 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2348 PVE
::Cluster
::check_cfs_quorum
();
2350 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2352 # exclusive lock if VM is running - else shared lock is enough;
2353 my $shared_lock = $running ?
0 : 1;
2357 # do all tests after lock
2358 # we also try to do all tests before we fork the worker
2360 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2362 PVE
::QemuConfig-
>check_lock($conf);
2364 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2366 die "unexpected state change\n" if $verify_running != $running;
2368 die "snapshot '$snapname' does not exist\n"
2369 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2371 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2373 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2375 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2377 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2379 die "unable to create VM $newid: config file already exists\n"
2382 my $newconf = { lock => 'clone' };
2387 foreach my $opt (keys %$oldconf) {
2388 my $value = $oldconf->{$opt};
2390 # do not copy snapshot related info
2391 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2392 $opt eq 'vmstate' || $opt eq 'snapstate';
2394 # no need to copy unused images, because VMID(owner) changes anyways
2395 next if $opt =~ m/^unused\d+$/;
2397 # always change MAC! address
2398 if ($opt =~ m/^net(\d+)$/) {
2399 my $net = PVE
::QemuServer
::parse_net
($value);
2400 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2401 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2402 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2403 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2404 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2405 die "unable to parse drive options for '$opt'\n" if !$drive;
2406 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2407 $newconf->{$opt} = $value; # simply copy configuration
2409 if ($param->{full
}) {
2410 die "Full clone feature is not available"
2411 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2412 $fullclone->{$opt} = 1;
2414 # not full means clone instead of copy
2415 die "Linked clone feature is not available"
2416 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2418 $drives->{$opt} = $drive;
2419 push @$vollist, $drive->{file
};
2422 # copy everything else
2423 $newconf->{$opt} = $value;
2427 # auto generate a new uuid
2428 my ($uuid, $uuid_str);
2429 UUID
::generate
($uuid);
2430 UUID
::unparse
($uuid, $uuid_str);
2431 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2432 $smbios1->{uuid
} = $uuid_str;
2433 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2435 delete $newconf->{template
};
2437 if ($param->{name
}) {
2438 $newconf->{name
} = $param->{name
};
2440 if ($oldconf->{name
}) {
2441 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2443 $newconf->{name
} = "Copy-of-VM-$vmid";
2447 if ($param->{description
}) {
2448 $newconf->{description
} = $param->{description
};
2451 # create empty/temp config - this fails if VM already exists on other node
2452 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2457 my $newvollist = [];
2461 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2463 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2465 my $total_jobs = scalar(keys %{$drives});
2467 my $skipcomplete = 1;
2469 foreach my $opt (keys %$drives) {
2471 my $drive = $drives->{$opt};
2472 $skipcomplete = undef if $total_jobs == $i; #finish after last drive
2474 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2475 $newid, $storage, $format, $fullclone->{$opt}, $newvollist, $jobs, $skipcomplete);
2477 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2479 PVE
::QemuConfig-
>write_config($newid, $newconf);
2483 delete $newconf->{lock};
2484 PVE
::QemuConfig-
>write_config($newid, $newconf);
2487 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2488 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2489 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2491 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2492 die "Failed to move config to node '$target' - rename failed: $!\n"
2493 if !rename($conffile, $newconffile);
2496 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2501 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2503 sleep 1; # some storage like rbd need to wait before release volume - really?
2505 foreach my $volid (@$newvollist) {
2506 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2509 die "clone failed: $err";
2515 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2517 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2520 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2521 # Aquire exclusive lock lock for $newid
2522 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2527 __PACKAGE__-
>register_method({
2528 name
=> 'move_vm_disk',
2529 path
=> '{vmid}/move_disk',
2533 description
=> "Move volume to different storage.",
2535 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2537 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2538 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2542 additionalProperties
=> 0,
2544 node
=> get_standard_option
('pve-node'),
2545 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2548 description
=> "The disk you want to move.",
2549 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2551 storage
=> get_standard_option
('pve-storage-id', {
2552 description
=> "Target storage.",
2553 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2557 description
=> "Target Format.",
2558 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2563 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2569 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2577 description
=> "the task ID.",
2582 my $rpcenv = PVE
::RPCEnvironment
::get
();
2584 my $authuser = $rpcenv->get_user();
2586 my $node = extract_param
($param, 'node');
2588 my $vmid = extract_param
($param, 'vmid');
2590 my $digest = extract_param
($param, 'digest');
2592 my $disk = extract_param
($param, 'disk');
2594 my $storeid = extract_param
($param, 'storage');
2596 my $format = extract_param
($param, 'format');
2598 my $storecfg = PVE
::Storage
::config
();
2600 my $updatefn = sub {
2602 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2604 PVE
::QemuConfig-
>check_lock($conf);
2606 die "checksum missmatch (file change by other user?)\n"
2607 if $digest && $digest ne $conf->{digest
};
2609 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2611 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2613 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2615 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2618 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2619 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2623 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2624 (!$format || !$oldfmt || $oldfmt eq $format);
2626 # this only checks snapshots because $disk is passed!
2627 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2628 die "you can't move a disk with snapshots and delete the source\n"
2629 if $snapshotted && $param->{delete};
2631 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2633 my $running = PVE
::QemuServer
::check_running
($vmid);
2635 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2639 my $newvollist = [];
2642 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2644 warn "moving disk with snapshots, snapshots will not be moved!\n"
2647 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2648 $vmid, $storeid, $format, 1, $newvollist);
2650 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2652 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2654 PVE
::QemuConfig-
>write_config($vmid, $conf);
2657 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2658 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2665 foreach my $volid (@$newvollist) {
2666 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2669 die "storage migration failed: $err";
2672 if ($param->{delete}) {
2674 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2675 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2681 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2684 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2687 __PACKAGE__-
>register_method({
2688 name
=> 'migrate_vm',
2689 path
=> '{vmid}/migrate',
2693 description
=> "Migrate virtual machine. Creates a new migration task.",
2695 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2698 additionalProperties
=> 0,
2700 node
=> get_standard_option
('pve-node'),
2701 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2702 target
=> get_standard_option
('pve-node', {
2703 description
=> "Target node.",
2704 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2708 description
=> "Use online/live migration.",
2713 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2718 enum
=> ['secure', 'insecure'],
2719 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2722 migration_network
=> {
2723 type
=> 'string', format
=> 'CIDR',
2724 description
=> "CIDR of the (sub) network that is used for migration.",
2731 description
=> "the task ID.",
2736 my $rpcenv = PVE
::RPCEnvironment
::get
();
2738 my $authuser = $rpcenv->get_user();
2740 my $target = extract_param
($param, 'target');
2742 my $localnode = PVE
::INotify
::nodename
();
2743 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2745 PVE
::Cluster
::check_cfs_quorum
();
2747 PVE
::Cluster
::check_node_exists
($target);
2749 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2751 my $vmid = extract_param
($param, 'vmid');
2753 raise_param_exc
({ force
=> "Only root may use this option." })
2754 if $param->{force
} && $authuser ne 'root@pam';
2756 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2757 if $param->{migration_type
} && $authuser ne 'root@pam';
2759 # allow root only until better network permissions are available
2760 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2761 if $param->{migration_network
} && $authuser ne 'root@pam';
2764 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2766 # try to detect errors early
2768 PVE
::QemuConfig-
>check_lock($conf);
2770 if (PVE
::QemuServer
::check_running
($vmid)) {
2771 die "cant migrate running VM without --online\n"
2772 if !$param->{online
};
2775 my $storecfg = PVE
::Storage
::config
();
2776 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2778 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2783 my $service = "vm:$vmid";
2785 my $cmd = ['ha-manager', 'migrate', $service, $target];
2787 print "Executing HA migrate for VM $vmid to node $target\n";
2789 PVE
::Tools
::run_command
($cmd);
2794 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2801 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2804 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2809 __PACKAGE__-
>register_method({
2811 path
=> '{vmid}/monitor',
2815 description
=> "Execute Qemu monitor commands.",
2817 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2818 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2821 additionalProperties
=> 0,
2823 node
=> get_standard_option
('pve-node'),
2824 vmid
=> get_standard_option
('pve-vmid'),
2827 description
=> "The monitor command.",
2831 returns
=> { type
=> 'string'},
2835 my $rpcenv = PVE
::RPCEnvironment
::get
();
2836 my $authuser = $rpcenv->get_user();
2839 my $command = shift;
2840 return $command =~ m/^\s*info(\s+|$)/
2841 || $command =~ m/^\s*help\s*$/;
2844 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2845 if !&$is_ro($param->{command
});
2847 my $vmid = $param->{vmid
};
2849 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2853 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2855 $res = "ERROR: $@" if $@;
2860 my $guest_agent_commands = [
2868 'network-get-interfaces',
2871 'get-memory-blocks',
2872 'get-memory-block-info',
2879 __PACKAGE__-
>register_method({
2881 path
=> '{vmid}/agent',
2885 description
=> "Execute Qemu Guest Agent commands.",
2887 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2890 additionalProperties
=> 0,
2892 node
=> get_standard_option
('pve-node'),
2893 vmid
=> get_standard_option
('pve-vmid', {
2894 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2897 description
=> "The QGA command.",
2898 enum
=> $guest_agent_commands,
2904 description
=> "Returns an object with a single `result` property. The type of that
2905 property depends on the executed command.",
2910 my $vmid = $param->{vmid
};
2912 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2914 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2915 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2917 my $cmd = $param->{command
};
2919 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2921 return { result
=> $res };
2924 __PACKAGE__-
>register_method({
2925 name
=> 'resize_vm',
2926 path
=> '{vmid}/resize',
2930 description
=> "Extend volume size.",
2932 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2935 additionalProperties
=> 0,
2937 node
=> get_standard_option
('pve-node'),
2938 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2939 skiplock
=> get_standard_option
('skiplock'),
2942 description
=> "The disk you want to resize.",
2943 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2947 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2948 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.",
2952 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2958 returns
=> { type
=> 'null'},
2962 my $rpcenv = PVE
::RPCEnvironment
::get
();
2964 my $authuser = $rpcenv->get_user();
2966 my $node = extract_param
($param, 'node');
2968 my $vmid = extract_param
($param, 'vmid');
2970 my $digest = extract_param
($param, 'digest');
2972 my $disk = extract_param
($param, 'disk');
2974 my $sizestr = extract_param
($param, 'size');
2976 my $skiplock = extract_param
($param, 'skiplock');
2977 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2978 if $skiplock && $authuser ne 'root@pam';
2980 my $storecfg = PVE
::Storage
::config
();
2982 my $updatefn = sub {
2984 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2986 die "checksum missmatch (file change by other user?)\n"
2987 if $digest && $digest ne $conf->{digest
};
2988 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2990 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2992 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2994 my (undef, undef, undef, undef, undef, undef, $format) =
2995 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2997 die "can't resize volume: $disk if snapshot exists\n"
2998 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3000 my $volid = $drive->{file
};
3002 die "disk '$disk' has no associated volume\n" if !$volid;
3004 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3006 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3008 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3010 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3011 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3013 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3014 my ($ext, $newsize, $unit) = ($1, $2, $4);
3017 $newsize = $newsize * 1024;
3018 } elsif ($unit eq 'M') {
3019 $newsize = $newsize * 1024 * 1024;
3020 } elsif ($unit eq 'G') {
3021 $newsize = $newsize * 1024 * 1024 * 1024;
3022 } elsif ($unit eq 'T') {
3023 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3026 $newsize += $size if $ext;
3027 $newsize = int($newsize);
3029 die "unable to skrink disk size\n" if $newsize < $size;
3031 return if $size == $newsize;
3033 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3035 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3037 $drive->{size
} = $newsize;
3038 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3040 PVE
::QemuConfig-
>write_config($vmid, $conf);
3043 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3047 __PACKAGE__-
>register_method({
3048 name
=> 'snapshot_list',
3049 path
=> '{vmid}/snapshot',
3051 description
=> "List all snapshots.",
3053 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3056 protected
=> 1, # qemu pid files are only readable by root
3058 additionalProperties
=> 0,
3060 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3061 node
=> get_standard_option
('pve-node'),
3070 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3075 my $vmid = $param->{vmid
};
3077 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3078 my $snaphash = $conf->{snapshots
} || {};
3082 foreach my $name (keys %$snaphash) {
3083 my $d = $snaphash->{$name};
3086 snaptime
=> $d->{snaptime
} || 0,
3087 vmstate
=> $d->{vmstate
} ?
1 : 0,
3088 description
=> $d->{description
} || '',
3090 $item->{parent
} = $d->{parent
} if $d->{parent
};
3091 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3095 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3096 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3097 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3099 push @$res, $current;
3104 __PACKAGE__-
>register_method({
3106 path
=> '{vmid}/snapshot',
3110 description
=> "Snapshot a VM.",
3112 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3115 additionalProperties
=> 0,
3117 node
=> get_standard_option
('pve-node'),
3118 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3119 snapname
=> get_standard_option
('pve-snapshot-name'),
3123 description
=> "Save the vmstate",
3128 description
=> "A textual description or comment.",
3134 description
=> "the task ID.",
3139 my $rpcenv = PVE
::RPCEnvironment
::get
();
3141 my $authuser = $rpcenv->get_user();
3143 my $node = extract_param
($param, 'node');
3145 my $vmid = extract_param
($param, 'vmid');
3147 my $snapname = extract_param
($param, 'snapname');
3149 die "unable to use snapshot name 'current' (reserved name)\n"
3150 if $snapname eq 'current';
3153 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3154 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3155 $param->{description
});
3158 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3161 __PACKAGE__-
>register_method({
3162 name
=> 'snapshot_cmd_idx',
3163 path
=> '{vmid}/snapshot/{snapname}',
3170 additionalProperties
=> 0,
3172 vmid
=> get_standard_option
('pve-vmid'),
3173 node
=> get_standard_option
('pve-node'),
3174 snapname
=> get_standard_option
('pve-snapshot-name'),
3183 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3190 push @$res, { cmd
=> 'rollback' };
3191 push @$res, { cmd
=> 'config' };
3196 __PACKAGE__-
>register_method({
3197 name
=> 'update_snapshot_config',
3198 path
=> '{vmid}/snapshot/{snapname}/config',
3202 description
=> "Update snapshot metadata.",
3204 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3207 additionalProperties
=> 0,
3209 node
=> get_standard_option
('pve-node'),
3210 vmid
=> get_standard_option
('pve-vmid'),
3211 snapname
=> get_standard_option
('pve-snapshot-name'),
3215 description
=> "A textual description or comment.",
3219 returns
=> { type
=> 'null' },
3223 my $rpcenv = PVE
::RPCEnvironment
::get
();
3225 my $authuser = $rpcenv->get_user();
3227 my $vmid = extract_param
($param, 'vmid');
3229 my $snapname = extract_param
($param, 'snapname');
3231 return undef if !defined($param->{description
});
3233 my $updatefn = sub {
3235 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3237 PVE
::QemuConfig-
>check_lock($conf);
3239 my $snap = $conf->{snapshots
}->{$snapname};
3241 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3243 $snap->{description
} = $param->{description
} if defined($param->{description
});
3245 PVE
::QemuConfig-
>write_config($vmid, $conf);
3248 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3253 __PACKAGE__-
>register_method({
3254 name
=> 'get_snapshot_config',
3255 path
=> '{vmid}/snapshot/{snapname}/config',
3258 description
=> "Get snapshot configuration",
3260 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3263 additionalProperties
=> 0,
3265 node
=> get_standard_option
('pve-node'),
3266 vmid
=> get_standard_option
('pve-vmid'),
3267 snapname
=> get_standard_option
('pve-snapshot-name'),
3270 returns
=> { type
=> "object" },
3274 my $rpcenv = PVE
::RPCEnvironment
::get
();
3276 my $authuser = $rpcenv->get_user();
3278 my $vmid = extract_param
($param, 'vmid');
3280 my $snapname = extract_param
($param, 'snapname');
3282 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3284 my $snap = $conf->{snapshots
}->{$snapname};
3286 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3291 __PACKAGE__-
>register_method({
3293 path
=> '{vmid}/snapshot/{snapname}/rollback',
3297 description
=> "Rollback VM state to specified snapshot.",
3299 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3302 additionalProperties
=> 0,
3304 node
=> get_standard_option
('pve-node'),
3305 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3306 snapname
=> get_standard_option
('pve-snapshot-name'),
3311 description
=> "the task ID.",
3316 my $rpcenv = PVE
::RPCEnvironment
::get
();
3318 my $authuser = $rpcenv->get_user();
3320 my $node = extract_param
($param, 'node');
3322 my $vmid = extract_param
($param, 'vmid');
3324 my $snapname = extract_param
($param, 'snapname');
3327 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3328 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3331 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3334 __PACKAGE__-
>register_method({
3335 name
=> 'delsnapshot',
3336 path
=> '{vmid}/snapshot/{snapname}',
3340 description
=> "Delete a VM snapshot.",
3342 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3345 additionalProperties
=> 0,
3347 node
=> get_standard_option
('pve-node'),
3348 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3349 snapname
=> get_standard_option
('pve-snapshot-name'),
3353 description
=> "For removal from config file, even if removing disk snapshots fails.",
3359 description
=> "the task ID.",
3364 my $rpcenv = PVE
::RPCEnvironment
::get
();
3366 my $authuser = $rpcenv->get_user();
3368 my $node = extract_param
($param, 'node');
3370 my $vmid = extract_param
($param, 'vmid');
3372 my $snapname = extract_param
($param, 'snapname');
3375 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3376 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3379 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3382 __PACKAGE__-
>register_method({
3384 path
=> '{vmid}/template',
3388 description
=> "Create a Template.",
3390 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3391 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3394 additionalProperties
=> 0,
3396 node
=> get_standard_option
('pve-node'),
3397 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3401 description
=> "If you want to convert only 1 disk to base image.",
3402 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3407 returns
=> { type
=> 'null'},
3411 my $rpcenv = PVE
::RPCEnvironment
::get
();
3413 my $authuser = $rpcenv->get_user();
3415 my $node = extract_param
($param, 'node');
3417 my $vmid = extract_param
($param, 'vmid');
3419 my $disk = extract_param
($param, 'disk');
3421 my $updatefn = sub {
3423 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3425 PVE
::QemuConfig-
>check_lock($conf);
3427 die "unable to create template, because VM contains snapshots\n"
3428 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3430 die "you can't convert a template to a template\n"
3431 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3433 die "you can't convert a VM to template if VM is running\n"
3434 if PVE
::QemuServer
::check_running
($vmid);
3437 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3440 $conf->{template
} = 1;
3441 PVE
::QemuConfig-
>write_config($vmid, $conf);
3443 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3446 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);