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'),
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 $machine = extract_param
($param, 'machine');
1663 my $stateuri = extract_param
($param, 'stateuri');
1664 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1665 if $stateuri && $authuser ne 'root@pam';
1667 my $skiplock = extract_param
($param, 'skiplock');
1668 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1669 if $skiplock && $authuser ne 'root@pam';
1671 my $migratedfrom = extract_param
($param, 'migratedfrom');
1672 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1673 if $migratedfrom && $authuser ne 'root@pam';
1675 my $migration_type = extract_param
($param, 'migration_type');
1676 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1677 if $migration_type && $authuser ne 'root@pam';
1679 my $migration_network = extract_param
($param, 'migration_network');
1680 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1681 if $migration_network && $authuser ne 'root@pam';
1683 # read spice ticket from STDIN
1685 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1686 if (defined(my $line = <>)) {
1688 $spice_ticket = $line;
1692 PVE
::Cluster
::check_cfs_quorum
();
1694 my $storecfg = PVE
::Storage
::config
();
1696 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1697 $rpcenv->{type
} ne 'ha') {
1702 my $service = "vm:$vmid";
1704 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1706 print "Executing HA start for VM $vmid\n";
1708 PVE
::Tools
::run_command
($cmd);
1713 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1720 syslog
('info', "start VM $vmid: $upid\n");
1722 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1723 $machine, $spice_ticket, $migration_network, $migration_type);
1728 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1732 __PACKAGE__-
>register_method({
1734 path
=> '{vmid}/status/stop',
1738 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1739 "is akin to pulling the power plug of a running computer and may damage the VM data",
1741 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1744 additionalProperties
=> 0,
1746 node
=> get_standard_option
('pve-node'),
1747 vmid
=> get_standard_option
('pve-vmid',
1748 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1749 skiplock
=> get_standard_option
('skiplock'),
1750 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1752 description
=> "Wait maximal timeout seconds.",
1758 description
=> "Do not deactivate storage volumes.",
1771 my $rpcenv = PVE
::RPCEnvironment
::get
();
1773 my $authuser = $rpcenv->get_user();
1775 my $node = extract_param
($param, 'node');
1777 my $vmid = extract_param
($param, 'vmid');
1779 my $skiplock = extract_param
($param, 'skiplock');
1780 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1781 if $skiplock && $authuser ne 'root@pam';
1783 my $keepActive = extract_param
($param, 'keepActive');
1784 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1785 if $keepActive && $authuser ne 'root@pam';
1787 my $migratedfrom = extract_param
($param, 'migratedfrom');
1788 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1789 if $migratedfrom && $authuser ne 'root@pam';
1792 my $storecfg = PVE
::Storage
::config
();
1794 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1799 my $service = "vm:$vmid";
1801 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1803 print "Executing HA stop for VM $vmid\n";
1805 PVE
::Tools
::run_command
($cmd);
1810 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1816 syslog
('info', "stop VM $vmid: $upid\n");
1818 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1819 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1824 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1828 __PACKAGE__-
>register_method({
1830 path
=> '{vmid}/status/reset',
1834 description
=> "Reset virtual machine.",
1836 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1839 additionalProperties
=> 0,
1841 node
=> get_standard_option
('pve-node'),
1842 vmid
=> get_standard_option
('pve-vmid',
1843 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1844 skiplock
=> get_standard_option
('skiplock'),
1853 my $rpcenv = PVE
::RPCEnvironment
::get
();
1855 my $authuser = $rpcenv->get_user();
1857 my $node = extract_param
($param, 'node');
1859 my $vmid = extract_param
($param, 'vmid');
1861 my $skiplock = extract_param
($param, 'skiplock');
1862 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1863 if $skiplock && $authuser ne 'root@pam';
1865 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1870 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1875 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1878 __PACKAGE__-
>register_method({
1879 name
=> 'vm_shutdown',
1880 path
=> '{vmid}/status/shutdown',
1884 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1885 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1887 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1890 additionalProperties
=> 0,
1892 node
=> get_standard_option
('pve-node'),
1893 vmid
=> get_standard_option
('pve-vmid',
1894 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1895 skiplock
=> get_standard_option
('skiplock'),
1897 description
=> "Wait maximal timeout seconds.",
1903 description
=> "Make sure the VM stops.",
1909 description
=> "Do not deactivate storage volumes.",
1922 my $rpcenv = PVE
::RPCEnvironment
::get
();
1924 my $authuser = $rpcenv->get_user();
1926 my $node = extract_param
($param, 'node');
1928 my $vmid = extract_param
($param, 'vmid');
1930 my $skiplock = extract_param
($param, 'skiplock');
1931 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1932 if $skiplock && $authuser ne 'root@pam';
1934 my $keepActive = extract_param
($param, 'keepActive');
1935 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1936 if $keepActive && $authuser ne 'root@pam';
1938 my $storecfg = PVE
::Storage
::config
();
1942 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1943 # otherwise, we will infer a shutdown command, but run into the timeout,
1944 # then when the vm is resumed, it will instantly shutdown
1946 # checking the qmp status here to get feedback to the gui/cli/api
1947 # and the status query should not take too long
1950 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1954 if (!$err && $qmpstatus->{status
} eq "paused") {
1955 if ($param->{forceStop
}) {
1956 warn "VM is paused - stop instead of shutdown\n";
1959 die "VM is paused - cannot shutdown\n";
1963 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
1964 ($rpcenv->{type
} ne 'ha')) {
1969 my $service = "vm:$vmid";
1971 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1973 print "Executing HA stop for VM $vmid\n";
1975 PVE
::Tools
::run_command
($cmd);
1980 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1987 syslog
('info', "shutdown VM $vmid: $upid\n");
1989 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1990 $shutdown, $param->{forceStop
}, $keepActive);
1995 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1999 __PACKAGE__-
>register_method({
2000 name
=> 'vm_suspend',
2001 path
=> '{vmid}/status/suspend',
2005 description
=> "Suspend virtual machine.",
2007 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2010 additionalProperties
=> 0,
2012 node
=> get_standard_option
('pve-node'),
2013 vmid
=> get_standard_option
('pve-vmid',
2014 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2015 skiplock
=> get_standard_option
('skiplock'),
2024 my $rpcenv = PVE
::RPCEnvironment
::get
();
2026 my $authuser = $rpcenv->get_user();
2028 my $node = extract_param
($param, 'node');
2030 my $vmid = extract_param
($param, 'vmid');
2032 my $skiplock = extract_param
($param, 'skiplock');
2033 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2034 if $skiplock && $authuser ne 'root@pam';
2036 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2041 syslog
('info', "suspend VM $vmid: $upid\n");
2043 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2048 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2051 __PACKAGE__-
>register_method({
2052 name
=> 'vm_resume',
2053 path
=> '{vmid}/status/resume',
2057 description
=> "Resume virtual machine.",
2059 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2062 additionalProperties
=> 0,
2064 node
=> get_standard_option
('pve-node'),
2065 vmid
=> get_standard_option
('pve-vmid',
2066 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2067 skiplock
=> get_standard_option
('skiplock'),
2068 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2078 my $rpcenv = PVE
::RPCEnvironment
::get
();
2080 my $authuser = $rpcenv->get_user();
2082 my $node = extract_param
($param, 'node');
2084 my $vmid = extract_param
($param, 'vmid');
2086 my $skiplock = extract_param
($param, 'skiplock');
2087 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2088 if $skiplock && $authuser ne 'root@pam';
2090 my $nocheck = extract_param
($param, 'nocheck');
2092 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2097 syslog
('info', "resume VM $vmid: $upid\n");
2099 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2104 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2107 __PACKAGE__-
>register_method({
2108 name
=> 'vm_sendkey',
2109 path
=> '{vmid}/sendkey',
2113 description
=> "Send key event to virtual machine.",
2115 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2118 additionalProperties
=> 0,
2120 node
=> get_standard_option
('pve-node'),
2121 vmid
=> get_standard_option
('pve-vmid',
2122 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2123 skiplock
=> get_standard_option
('skiplock'),
2125 description
=> "The key (qemu monitor encoding).",
2130 returns
=> { type
=> 'null'},
2134 my $rpcenv = PVE
::RPCEnvironment
::get
();
2136 my $authuser = $rpcenv->get_user();
2138 my $node = extract_param
($param, 'node');
2140 my $vmid = extract_param
($param, 'vmid');
2142 my $skiplock = extract_param
($param, 'skiplock');
2143 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2144 if $skiplock && $authuser ne 'root@pam';
2146 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2151 __PACKAGE__-
>register_method({
2152 name
=> 'vm_feature',
2153 path
=> '{vmid}/feature',
2157 description
=> "Check if feature for virtual machine is available.",
2159 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2162 additionalProperties
=> 0,
2164 node
=> get_standard_option
('pve-node'),
2165 vmid
=> get_standard_option
('pve-vmid'),
2167 description
=> "Feature to check.",
2169 enum
=> [ 'snapshot', 'clone', 'copy' ],
2171 snapname
=> get_standard_option
('pve-snapshot-name', {
2179 hasFeature
=> { type
=> 'boolean' },
2182 items
=> { type
=> 'string' },
2189 my $node = extract_param
($param, 'node');
2191 my $vmid = extract_param
($param, 'vmid');
2193 my $snapname = extract_param
($param, 'snapname');
2195 my $feature = extract_param
($param, 'feature');
2197 my $running = PVE
::QemuServer
::check_running
($vmid);
2199 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2202 my $snap = $conf->{snapshots
}->{$snapname};
2203 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2206 my $storecfg = PVE
::Storage
::config
();
2208 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2209 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2212 hasFeature
=> $hasFeature,
2213 nodes
=> [ keys %$nodelist ],
2217 __PACKAGE__-
>register_method({
2219 path
=> '{vmid}/clone',
2223 description
=> "Create a copy of virtual machine/template.",
2225 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2226 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2227 "'Datastore.AllocateSpace' on any used storage.",
2230 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2232 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2233 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2238 additionalProperties
=> 0,
2240 node
=> get_standard_option
('pve-node'),
2241 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2242 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2245 type
=> 'string', format
=> 'dns-name',
2246 description
=> "Set a name for the new VM.",
2251 description
=> "Description for the new VM.",
2255 type
=> 'string', format
=> 'pve-poolid',
2256 description
=> "Add the new VM to the specified pool.",
2258 snapname
=> get_standard_option
('pve-snapshot-name', {
2261 storage
=> get_standard_option
('pve-storage-id', {
2262 description
=> "Target storage for full clone.",
2267 description
=> "Target format for file storage.",
2271 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2276 description
=> "Create a full copy of all disk. This is always done when " .
2277 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2280 target
=> get_standard_option
('pve-node', {
2281 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2292 my $rpcenv = PVE
::RPCEnvironment
::get
();
2294 my $authuser = $rpcenv->get_user();
2296 my $node = extract_param
($param, 'node');
2298 my $vmid = extract_param
($param, 'vmid');
2300 my $newid = extract_param
($param, 'newid');
2302 my $pool = extract_param
($param, 'pool');
2304 if (defined($pool)) {
2305 $rpcenv->check_pool_exist($pool);
2308 my $snapname = extract_param
($param, 'snapname');
2310 my $storage = extract_param
($param, 'storage');
2312 my $format = extract_param
($param, 'format');
2314 my $target = extract_param
($param, 'target');
2316 my $localnode = PVE
::INotify
::nodename
();
2318 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2320 PVE
::Cluster
::check_node_exists
($target) if $target;
2322 my $storecfg = PVE
::Storage
::config
();
2325 # check if storage is enabled on local node
2326 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2328 # check if storage is available on target node
2329 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2330 # clone only works if target storage is shared
2331 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2332 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2336 PVE
::Cluster
::check_cfs_quorum
();
2338 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2340 # exclusive lock if VM is running - else shared lock is enough;
2341 my $shared_lock = $running ?
0 : 1;
2345 # do all tests after lock
2346 # we also try to do all tests before we fork the worker
2348 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2350 PVE
::QemuConfig-
>check_lock($conf);
2352 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2354 die "unexpected state change\n" if $verify_running != $running;
2356 die "snapshot '$snapname' does not exist\n"
2357 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2359 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2361 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2363 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2365 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2367 die "unable to create VM $newid: config file already exists\n"
2370 my $newconf = { lock => 'clone' };
2375 foreach my $opt (keys %$oldconf) {
2376 my $value = $oldconf->{$opt};
2378 # do not copy snapshot related info
2379 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2380 $opt eq 'vmstate' || $opt eq 'snapstate';
2382 # no need to copy unused images, because VMID(owner) changes anyways
2383 next if $opt =~ m/^unused\d+$/;
2385 # always change MAC! address
2386 if ($opt =~ m/^net(\d+)$/) {
2387 my $net = PVE
::QemuServer
::parse_net
($value);
2388 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2389 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2390 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2391 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2392 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2393 die "unable to parse drive options for '$opt'\n" if !$drive;
2394 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2395 $newconf->{$opt} = $value; # simply copy configuration
2397 if ($param->{full
}) {
2398 die "Full clone feature is not available"
2399 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2400 $fullclone->{$opt} = 1;
2402 # not full means clone instead of copy
2403 die "Linked clone feature is not available"
2404 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2406 $drives->{$opt} = $drive;
2407 push @$vollist, $drive->{file
};
2410 # copy everything else
2411 $newconf->{$opt} = $value;
2415 # auto generate a new uuid
2416 my ($uuid, $uuid_str);
2417 UUID
::generate
($uuid);
2418 UUID
::unparse
($uuid, $uuid_str);
2419 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2420 $smbios1->{uuid
} = $uuid_str;
2421 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2423 delete $newconf->{template
};
2425 if ($param->{name
}) {
2426 $newconf->{name
} = $param->{name
};
2428 if ($oldconf->{name
}) {
2429 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2431 $newconf->{name
} = "Copy-of-VM-$vmid";
2435 if ($param->{description
}) {
2436 $newconf->{description
} = $param->{description
};
2439 # create empty/temp config - this fails if VM already exists on other node
2440 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2445 my $newvollist = [];
2449 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2451 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2453 my $total_jobs = scalar(keys %{$drives});
2455 my $skipcomplete = 1;
2457 foreach my $opt (keys %$drives) {
2459 my $drive = $drives->{$opt};
2460 $skipcomplete = undef if $total_jobs == $i; #finish after last drive
2462 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2463 $newid, $storage, $format, $fullclone->{$opt}, $newvollist, $jobs, $skipcomplete);
2465 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2467 PVE
::QemuConfig-
>write_config($newid, $newconf);
2471 delete $newconf->{lock};
2472 PVE
::QemuConfig-
>write_config($newid, $newconf);
2475 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2476 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2477 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2479 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2480 die "Failed to move config to node '$target' - rename failed: $!\n"
2481 if !rename($conffile, $newconffile);
2484 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2489 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2491 sleep 1; # some storage like rbd need to wait before release volume - really?
2493 foreach my $volid (@$newvollist) {
2494 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2497 die "clone failed: $err";
2503 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2505 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2508 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2509 # Aquire exclusive lock lock for $newid
2510 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2515 __PACKAGE__-
>register_method({
2516 name
=> 'move_vm_disk',
2517 path
=> '{vmid}/move_disk',
2521 description
=> "Move volume to different storage.",
2523 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2525 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2526 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2530 additionalProperties
=> 0,
2532 node
=> get_standard_option
('pve-node'),
2533 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2536 description
=> "The disk you want to move.",
2537 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2539 storage
=> get_standard_option
('pve-storage-id', {
2540 description
=> "Target storage.",
2541 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2545 description
=> "Target Format.",
2546 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2551 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2557 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2565 description
=> "the task ID.",
2570 my $rpcenv = PVE
::RPCEnvironment
::get
();
2572 my $authuser = $rpcenv->get_user();
2574 my $node = extract_param
($param, 'node');
2576 my $vmid = extract_param
($param, 'vmid');
2578 my $digest = extract_param
($param, 'digest');
2580 my $disk = extract_param
($param, 'disk');
2582 my $storeid = extract_param
($param, 'storage');
2584 my $format = extract_param
($param, 'format');
2586 my $storecfg = PVE
::Storage
::config
();
2588 my $updatefn = sub {
2590 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2592 PVE
::QemuConfig-
>check_lock($conf);
2594 die "checksum missmatch (file change by other user?)\n"
2595 if $digest && $digest ne $conf->{digest
};
2597 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2599 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2601 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2603 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2606 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2607 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2611 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2612 (!$format || !$oldfmt || $oldfmt eq $format);
2614 # this only checks snapshots because $disk is passed!
2615 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2616 die "you can't move a disk with snapshots and delete the source\n"
2617 if $snapshotted && $param->{delete};
2619 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2621 my $running = PVE
::QemuServer
::check_running
($vmid);
2623 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2627 my $newvollist = [];
2630 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2632 warn "moving disk with snapshots, snapshots will not be moved!\n"
2635 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2636 $vmid, $storeid, $format, 1, $newvollist);
2638 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2640 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2642 PVE
::QemuConfig-
>write_config($vmid, $conf);
2645 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2646 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2653 foreach my $volid (@$newvollist) {
2654 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2657 die "storage migration failed: $err";
2660 if ($param->{delete}) {
2662 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2663 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2669 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2672 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2675 __PACKAGE__-
>register_method({
2676 name
=> 'migrate_vm',
2677 path
=> '{vmid}/migrate',
2681 description
=> "Migrate virtual machine. Creates a new migration task.",
2683 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2686 additionalProperties
=> 0,
2688 node
=> get_standard_option
('pve-node'),
2689 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2690 target
=> get_standard_option
('pve-node', {
2691 description
=> "Target node.",
2692 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2696 description
=> "Use online/live migration.",
2701 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2706 enum
=> ['secure', 'insecure'],
2707 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2710 migration_network
=> {
2711 type
=> 'string', format
=> 'CIDR',
2712 description
=> "CIDR of the (sub) network that is used for migration.",
2719 description
=> "the task ID.",
2724 my $rpcenv = PVE
::RPCEnvironment
::get
();
2726 my $authuser = $rpcenv->get_user();
2728 my $target = extract_param
($param, 'target');
2730 my $localnode = PVE
::INotify
::nodename
();
2731 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2733 PVE
::Cluster
::check_cfs_quorum
();
2735 PVE
::Cluster
::check_node_exists
($target);
2737 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2739 my $vmid = extract_param
($param, 'vmid');
2741 raise_param_exc
({ force
=> "Only root may use this option." })
2742 if $param->{force
} && $authuser ne 'root@pam';
2744 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2745 if $param->{migration_type
} && $authuser ne 'root@pam';
2747 # allow root only until better network permissions are available
2748 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2749 if $param->{migration_network
} && $authuser ne 'root@pam';
2752 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2754 # try to detect errors early
2756 PVE
::QemuConfig-
>check_lock($conf);
2758 if (PVE
::QemuServer
::check_running
($vmid)) {
2759 die "cant migrate running VM without --online\n"
2760 if !$param->{online
};
2763 my $storecfg = PVE
::Storage
::config
();
2764 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2766 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2771 my $service = "vm:$vmid";
2773 my $cmd = ['ha-manager', 'migrate', $service, $target];
2775 print "Executing HA migrate for VM $vmid to node $target\n";
2777 PVE
::Tools
::run_command
($cmd);
2782 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2789 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2792 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2797 __PACKAGE__-
>register_method({
2799 path
=> '{vmid}/monitor',
2803 description
=> "Execute Qemu monitor commands.",
2805 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2806 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2809 additionalProperties
=> 0,
2811 node
=> get_standard_option
('pve-node'),
2812 vmid
=> get_standard_option
('pve-vmid'),
2815 description
=> "The monitor command.",
2819 returns
=> { type
=> 'string'},
2823 my $rpcenv = PVE
::RPCEnvironment
::get
();
2824 my $authuser = $rpcenv->get_user();
2827 my $command = shift;
2828 return $command =~ m/^\s*info(\s+|$)/
2829 || $command =~ m/^\s*help\s*$/;
2832 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2833 if !&$is_ro($param->{command
});
2835 my $vmid = $param->{vmid
};
2837 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2841 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2843 $res = "ERROR: $@" if $@;
2848 my $guest_agent_commands = [
2856 'network-get-interfaces',
2859 'get-memory-blocks',
2860 'get-memory-block-info',
2867 __PACKAGE__-
>register_method({
2869 path
=> '{vmid}/agent',
2873 description
=> "Execute Qemu Guest Agent commands.",
2875 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2878 additionalProperties
=> 0,
2880 node
=> get_standard_option
('pve-node'),
2881 vmid
=> get_standard_option
('pve-vmid', {
2882 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2885 description
=> "The QGA command.",
2886 enum
=> $guest_agent_commands,
2892 description
=> "Returns an object with a single `result` property. The type of that
2893 property depends on the executed command.",
2898 my $vmid = $param->{vmid
};
2900 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2902 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2903 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2905 my $cmd = $param->{command
};
2907 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2909 return { result
=> $res };
2912 __PACKAGE__-
>register_method({
2913 name
=> 'resize_vm',
2914 path
=> '{vmid}/resize',
2918 description
=> "Extend volume size.",
2920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2923 additionalProperties
=> 0,
2925 node
=> get_standard_option
('pve-node'),
2926 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2927 skiplock
=> get_standard_option
('skiplock'),
2930 description
=> "The disk you want to resize.",
2931 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2935 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2936 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.",
2940 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2946 returns
=> { type
=> 'null'},
2950 my $rpcenv = PVE
::RPCEnvironment
::get
();
2952 my $authuser = $rpcenv->get_user();
2954 my $node = extract_param
($param, 'node');
2956 my $vmid = extract_param
($param, 'vmid');
2958 my $digest = extract_param
($param, 'digest');
2960 my $disk = extract_param
($param, 'disk');
2962 my $sizestr = extract_param
($param, 'size');
2964 my $skiplock = extract_param
($param, 'skiplock');
2965 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2966 if $skiplock && $authuser ne 'root@pam';
2968 my $storecfg = PVE
::Storage
::config
();
2970 my $updatefn = sub {
2972 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2974 die "checksum missmatch (file change by other user?)\n"
2975 if $digest && $digest ne $conf->{digest
};
2976 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2978 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2980 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2982 my (undef, undef, undef, undef, undef, undef, $format) =
2983 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2985 die "can't resize volume: $disk if snapshot exists\n"
2986 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2988 my $volid = $drive->{file
};
2990 die "disk '$disk' has no associated volume\n" if !$volid;
2992 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2994 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2996 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2998 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2999 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3001 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3002 my ($ext, $newsize, $unit) = ($1, $2, $4);
3005 $newsize = $newsize * 1024;
3006 } elsif ($unit eq 'M') {
3007 $newsize = $newsize * 1024 * 1024;
3008 } elsif ($unit eq 'G') {
3009 $newsize = $newsize * 1024 * 1024 * 1024;
3010 } elsif ($unit eq 'T') {
3011 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3014 $newsize += $size if $ext;
3015 $newsize = int($newsize);
3017 die "unable to skrink disk size\n" if $newsize < $size;
3019 return if $size == $newsize;
3021 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3023 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3025 $drive->{size
} = $newsize;
3026 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3028 PVE
::QemuConfig-
>write_config($vmid, $conf);
3031 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3035 __PACKAGE__-
>register_method({
3036 name
=> 'snapshot_list',
3037 path
=> '{vmid}/snapshot',
3039 description
=> "List all snapshots.",
3041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3044 protected
=> 1, # qemu pid files are only readable by root
3046 additionalProperties
=> 0,
3048 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3049 node
=> get_standard_option
('pve-node'),
3058 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3063 my $vmid = $param->{vmid
};
3065 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3066 my $snaphash = $conf->{snapshots
} || {};
3070 foreach my $name (keys %$snaphash) {
3071 my $d = $snaphash->{$name};
3074 snaptime
=> $d->{snaptime
} || 0,
3075 vmstate
=> $d->{vmstate
} ?
1 : 0,
3076 description
=> $d->{description
} || '',
3078 $item->{parent
} = $d->{parent
} if $d->{parent
};
3079 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3083 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3084 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3085 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3087 push @$res, $current;
3092 __PACKAGE__-
>register_method({
3094 path
=> '{vmid}/snapshot',
3098 description
=> "Snapshot a VM.",
3100 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3103 additionalProperties
=> 0,
3105 node
=> get_standard_option
('pve-node'),
3106 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3107 snapname
=> get_standard_option
('pve-snapshot-name'),
3111 description
=> "Save the vmstate",
3116 description
=> "A textual description or comment.",
3122 description
=> "the task ID.",
3127 my $rpcenv = PVE
::RPCEnvironment
::get
();
3129 my $authuser = $rpcenv->get_user();
3131 my $node = extract_param
($param, 'node');
3133 my $vmid = extract_param
($param, 'vmid');
3135 my $snapname = extract_param
($param, 'snapname');
3137 die "unable to use snapshot name 'current' (reserved name)\n"
3138 if $snapname eq 'current';
3141 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3142 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3143 $param->{description
});
3146 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3149 __PACKAGE__-
>register_method({
3150 name
=> 'snapshot_cmd_idx',
3151 path
=> '{vmid}/snapshot/{snapname}',
3158 additionalProperties
=> 0,
3160 vmid
=> get_standard_option
('pve-vmid'),
3161 node
=> get_standard_option
('pve-node'),
3162 snapname
=> get_standard_option
('pve-snapshot-name'),
3171 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3178 push @$res, { cmd
=> 'rollback' };
3179 push @$res, { cmd
=> 'config' };
3184 __PACKAGE__-
>register_method({
3185 name
=> 'update_snapshot_config',
3186 path
=> '{vmid}/snapshot/{snapname}/config',
3190 description
=> "Update snapshot metadata.",
3192 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3195 additionalProperties
=> 0,
3197 node
=> get_standard_option
('pve-node'),
3198 vmid
=> get_standard_option
('pve-vmid'),
3199 snapname
=> get_standard_option
('pve-snapshot-name'),
3203 description
=> "A textual description or comment.",
3207 returns
=> { type
=> 'null' },
3211 my $rpcenv = PVE
::RPCEnvironment
::get
();
3213 my $authuser = $rpcenv->get_user();
3215 my $vmid = extract_param
($param, 'vmid');
3217 my $snapname = extract_param
($param, 'snapname');
3219 return undef if !defined($param->{description
});
3221 my $updatefn = sub {
3223 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3225 PVE
::QemuConfig-
>check_lock($conf);
3227 my $snap = $conf->{snapshots
}->{$snapname};
3229 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3231 $snap->{description
} = $param->{description
} if defined($param->{description
});
3233 PVE
::QemuConfig-
>write_config($vmid, $conf);
3236 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3241 __PACKAGE__-
>register_method({
3242 name
=> 'get_snapshot_config',
3243 path
=> '{vmid}/snapshot/{snapname}/config',
3246 description
=> "Get snapshot configuration",
3248 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3251 additionalProperties
=> 0,
3253 node
=> get_standard_option
('pve-node'),
3254 vmid
=> get_standard_option
('pve-vmid'),
3255 snapname
=> get_standard_option
('pve-snapshot-name'),
3258 returns
=> { type
=> "object" },
3262 my $rpcenv = PVE
::RPCEnvironment
::get
();
3264 my $authuser = $rpcenv->get_user();
3266 my $vmid = extract_param
($param, 'vmid');
3268 my $snapname = extract_param
($param, 'snapname');
3270 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3272 my $snap = $conf->{snapshots
}->{$snapname};
3274 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3279 __PACKAGE__-
>register_method({
3281 path
=> '{vmid}/snapshot/{snapname}/rollback',
3285 description
=> "Rollback VM state to specified snapshot.",
3287 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3290 additionalProperties
=> 0,
3292 node
=> get_standard_option
('pve-node'),
3293 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3294 snapname
=> get_standard_option
('pve-snapshot-name'),
3299 description
=> "the task ID.",
3304 my $rpcenv = PVE
::RPCEnvironment
::get
();
3306 my $authuser = $rpcenv->get_user();
3308 my $node = extract_param
($param, 'node');
3310 my $vmid = extract_param
($param, 'vmid');
3312 my $snapname = extract_param
($param, 'snapname');
3315 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3316 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3319 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3322 __PACKAGE__-
>register_method({
3323 name
=> 'delsnapshot',
3324 path
=> '{vmid}/snapshot/{snapname}',
3328 description
=> "Delete a VM snapshot.",
3330 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3333 additionalProperties
=> 0,
3335 node
=> get_standard_option
('pve-node'),
3336 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3337 snapname
=> get_standard_option
('pve-snapshot-name'),
3341 description
=> "For removal from config file, even if removing disk snapshots fails.",
3347 description
=> "the task ID.",
3352 my $rpcenv = PVE
::RPCEnvironment
::get
();
3354 my $authuser = $rpcenv->get_user();
3356 my $node = extract_param
($param, 'node');
3358 my $vmid = extract_param
($param, 'vmid');
3360 my $snapname = extract_param
($param, 'snapname');
3363 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3364 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3367 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3370 __PACKAGE__-
>register_method({
3372 path
=> '{vmid}/template',
3376 description
=> "Create a Template.",
3378 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3379 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3382 additionalProperties
=> 0,
3384 node
=> get_standard_option
('pve-node'),
3385 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3389 description
=> "If you want to convert only 1 disk to base image.",
3390 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3395 returns
=> { type
=> 'null'},
3399 my $rpcenv = PVE
::RPCEnvironment
::get
();
3401 my $authuser = $rpcenv->get_user();
3403 my $node = extract_param
($param, 'node');
3405 my $vmid = extract_param
($param, 'vmid');
3407 my $disk = extract_param
($param, 'disk');
3409 my $updatefn = sub {
3411 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3413 PVE
::QemuConfig-
>check_lock($conf);
3415 die "unable to create template, because VM contains snapshots\n"
3416 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3418 die "you can't convert a template to a template\n"
3419 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3421 die "you can't convert a VM to template if VM is running\n"
3422 if PVE
::QemuServer
::check_running
($vmid);
3425 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3428 $conf->{template
} = 1;
3429 PVE
::QemuConfig-
>write_config($vmid, $conf);
3431 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3434 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);