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 = [];
2448 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2450 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2452 foreach my $opt (keys %$drives) {
2453 my $drive = $drives->{$opt};
2455 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2456 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2458 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2460 PVE
::QemuConfig-
>write_config($newid, $newconf);
2463 delete $newconf->{lock};
2464 PVE
::QemuConfig-
>write_config($newid, $newconf);
2467 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2468 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2469 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2471 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2472 die "Failed to move config to node '$target' - rename failed: $!\n"
2473 if !rename($conffile, $newconffile);
2476 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2481 sleep 1; # some storage like rbd need to wait before release volume - really?
2483 foreach my $volid (@$newvollist) {
2484 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2487 die "clone failed: $err";
2493 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2495 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2498 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2499 # Aquire exclusive lock lock for $newid
2500 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2505 __PACKAGE__-
>register_method({
2506 name
=> 'move_vm_disk',
2507 path
=> '{vmid}/move_disk',
2511 description
=> "Move volume to different storage.",
2513 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2515 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2516 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2520 additionalProperties
=> 0,
2522 node
=> get_standard_option
('pve-node'),
2523 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2526 description
=> "The disk you want to move.",
2527 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2529 storage
=> get_standard_option
('pve-storage-id', {
2530 description
=> "Target storage.",
2531 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2535 description
=> "Target Format.",
2536 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2541 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2547 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2555 description
=> "the task ID.",
2560 my $rpcenv = PVE
::RPCEnvironment
::get
();
2562 my $authuser = $rpcenv->get_user();
2564 my $node = extract_param
($param, 'node');
2566 my $vmid = extract_param
($param, 'vmid');
2568 my $digest = extract_param
($param, 'digest');
2570 my $disk = extract_param
($param, 'disk');
2572 my $storeid = extract_param
($param, 'storage');
2574 my $format = extract_param
($param, 'format');
2576 my $storecfg = PVE
::Storage
::config
();
2578 my $updatefn = sub {
2580 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2582 PVE
::QemuConfig-
>check_lock($conf);
2584 die "checksum missmatch (file change by other user?)\n"
2585 if $digest && $digest ne $conf->{digest
};
2587 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2589 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2591 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2593 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2596 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2597 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2601 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2602 (!$format || !$oldfmt || $oldfmt eq $format);
2604 # this only checks snapshots because $disk is passed!
2605 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2606 die "you can't move a disk with snapshots and delete the source\n"
2607 if $snapshotted && $param->{delete};
2609 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2611 my $running = PVE
::QemuServer
::check_running
($vmid);
2613 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2617 my $newvollist = [];
2620 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2622 warn "moving disk with snapshots, snapshots will not be moved!\n"
2625 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2626 $vmid, $storeid, $format, 1, $newvollist);
2628 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2630 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2632 PVE
::QemuConfig-
>write_config($vmid, $conf);
2635 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2636 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2643 foreach my $volid (@$newvollist) {
2644 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2647 die "storage migration failed: $err";
2650 if ($param->{delete}) {
2652 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2653 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2659 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2662 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2665 __PACKAGE__-
>register_method({
2666 name
=> 'migrate_vm',
2667 path
=> '{vmid}/migrate',
2671 description
=> "Migrate virtual machine. Creates a new migration task.",
2673 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2676 additionalProperties
=> 0,
2678 node
=> get_standard_option
('pve-node'),
2679 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2680 target
=> get_standard_option
('pve-node', {
2681 description
=> "Target node.",
2682 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2686 description
=> "Use online/live migration.",
2691 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2696 enum
=> ['secure', 'insecure'],
2697 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2700 migration_network
=> {
2701 type
=> 'string', format
=> 'CIDR',
2702 description
=> "CIDR of the (sub) network that is used for migration.",
2709 description
=> "the task ID.",
2714 my $rpcenv = PVE
::RPCEnvironment
::get
();
2716 my $authuser = $rpcenv->get_user();
2718 my $target = extract_param
($param, 'target');
2720 my $localnode = PVE
::INotify
::nodename
();
2721 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2723 PVE
::Cluster
::check_cfs_quorum
();
2725 PVE
::Cluster
::check_node_exists
($target);
2727 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2729 my $vmid = extract_param
($param, 'vmid');
2731 raise_param_exc
({ force
=> "Only root may use this option." })
2732 if $param->{force
} && $authuser ne 'root@pam';
2734 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2735 if $param->{migration_type
} && $authuser ne 'root@pam';
2737 # allow root only until better network permissions are available
2738 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2739 if $param->{migration_network
} && $authuser ne 'root@pam';
2742 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2744 # try to detect errors early
2746 PVE
::QemuConfig-
>check_lock($conf);
2748 if (PVE
::QemuServer
::check_running
($vmid)) {
2749 die "cant migrate running VM without --online\n"
2750 if !$param->{online
};
2753 my $storecfg = PVE
::Storage
::config
();
2754 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2756 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2761 my $service = "vm:$vmid";
2763 my $cmd = ['ha-manager', 'migrate', $service, $target];
2765 print "Executing HA migrate for VM $vmid to node $target\n";
2767 PVE
::Tools
::run_command
($cmd);
2772 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2779 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2782 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2787 __PACKAGE__-
>register_method({
2789 path
=> '{vmid}/monitor',
2793 description
=> "Execute Qemu monitor commands.",
2795 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2796 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2799 additionalProperties
=> 0,
2801 node
=> get_standard_option
('pve-node'),
2802 vmid
=> get_standard_option
('pve-vmid'),
2805 description
=> "The monitor command.",
2809 returns
=> { type
=> 'string'},
2813 my $rpcenv = PVE
::RPCEnvironment
::get
();
2814 my $authuser = $rpcenv->get_user();
2817 my $command = shift;
2818 return $command =~ m/^\s*info(\s+|$)/
2819 || $command =~ m/^\s*help\s*$/;
2822 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2823 if !&$is_ro($param->{command
});
2825 my $vmid = $param->{vmid
};
2827 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2831 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2833 $res = "ERROR: $@" if $@;
2838 __PACKAGE__-
>register_method({
2840 path
=> '{vmid}/agent',
2844 description
=> "Execute Qemu Guest Agent commands.",
2846 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2849 additionalProperties
=> 0,
2851 node
=> get_standard_option
('pve-node'),
2852 vmid
=> get_standard_option
('pve-vmid', {
2853 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2856 description
=> "The QGA command.",
2860 returns
=> { type
=> 'object' },
2864 my $vmid = $param->{vmid
};
2866 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2868 die "Only qga commands are allowed\n" if $param->{command
} !~ m/^guest-.*$/;
2869 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2870 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2874 $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, $param->{command
});
2878 return {'ERROR:', $err};
2880 return {'OK:', $res};
2884 __PACKAGE__-
>register_method({
2885 name
=> 'resize_vm',
2886 path
=> '{vmid}/resize',
2890 description
=> "Extend volume size.",
2892 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2895 additionalProperties
=> 0,
2897 node
=> get_standard_option
('pve-node'),
2898 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2899 skiplock
=> get_standard_option
('skiplock'),
2902 description
=> "The disk you want to resize.",
2903 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2907 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2908 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.",
2912 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2918 returns
=> { type
=> 'null'},
2922 my $rpcenv = PVE
::RPCEnvironment
::get
();
2924 my $authuser = $rpcenv->get_user();
2926 my $node = extract_param
($param, 'node');
2928 my $vmid = extract_param
($param, 'vmid');
2930 my $digest = extract_param
($param, 'digest');
2932 my $disk = extract_param
($param, 'disk');
2934 my $sizestr = extract_param
($param, 'size');
2936 my $skiplock = extract_param
($param, 'skiplock');
2937 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2938 if $skiplock && $authuser ne 'root@pam';
2940 my $storecfg = PVE
::Storage
::config
();
2942 my $updatefn = sub {
2944 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2946 die "checksum missmatch (file change by other user?)\n"
2947 if $digest && $digest ne $conf->{digest
};
2948 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2950 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2952 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2954 my (undef, undef, undef, undef, undef, undef, $format) =
2955 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2957 die "can't resize volume: $disk if snapshot exists\n"
2958 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2960 my $volid = $drive->{file
};
2962 die "disk '$disk' has no associated volume\n" if !$volid;
2964 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2966 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2968 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2970 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2971 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2973 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2974 my ($ext, $newsize, $unit) = ($1, $2, $4);
2977 $newsize = $newsize * 1024;
2978 } elsif ($unit eq 'M') {
2979 $newsize = $newsize * 1024 * 1024;
2980 } elsif ($unit eq 'G') {
2981 $newsize = $newsize * 1024 * 1024 * 1024;
2982 } elsif ($unit eq 'T') {
2983 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2986 $newsize += $size if $ext;
2987 $newsize = int($newsize);
2989 die "unable to skrink disk size\n" if $newsize < $size;
2991 return if $size == $newsize;
2993 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2995 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2997 $drive->{size
} = $newsize;
2998 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3000 PVE
::QemuConfig-
>write_config($vmid, $conf);
3003 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3007 __PACKAGE__-
>register_method({
3008 name
=> 'snapshot_list',
3009 path
=> '{vmid}/snapshot',
3011 description
=> "List all snapshots.",
3013 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3016 protected
=> 1, # qemu pid files are only readable by root
3018 additionalProperties
=> 0,
3020 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3021 node
=> get_standard_option
('pve-node'),
3030 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3035 my $vmid = $param->{vmid
};
3037 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3038 my $snaphash = $conf->{snapshots
} || {};
3042 foreach my $name (keys %$snaphash) {
3043 my $d = $snaphash->{$name};
3046 snaptime
=> $d->{snaptime
} || 0,
3047 vmstate
=> $d->{vmstate
} ?
1 : 0,
3048 description
=> $d->{description
} || '',
3050 $item->{parent
} = $d->{parent
} if $d->{parent
};
3051 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3055 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3056 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3057 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3059 push @$res, $current;
3064 __PACKAGE__-
>register_method({
3066 path
=> '{vmid}/snapshot',
3070 description
=> "Snapshot a VM.",
3072 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3075 additionalProperties
=> 0,
3077 node
=> get_standard_option
('pve-node'),
3078 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3079 snapname
=> get_standard_option
('pve-snapshot-name'),
3083 description
=> "Save the vmstate",
3088 description
=> "A textual description or comment.",
3094 description
=> "the task ID.",
3099 my $rpcenv = PVE
::RPCEnvironment
::get
();
3101 my $authuser = $rpcenv->get_user();
3103 my $node = extract_param
($param, 'node');
3105 my $vmid = extract_param
($param, 'vmid');
3107 my $snapname = extract_param
($param, 'snapname');
3109 die "unable to use snapshot name 'current' (reserved name)\n"
3110 if $snapname eq 'current';
3113 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3114 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3115 $param->{description
});
3118 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3121 __PACKAGE__-
>register_method({
3122 name
=> 'snapshot_cmd_idx',
3123 path
=> '{vmid}/snapshot/{snapname}',
3130 additionalProperties
=> 0,
3132 vmid
=> get_standard_option
('pve-vmid'),
3133 node
=> get_standard_option
('pve-node'),
3134 snapname
=> get_standard_option
('pve-snapshot-name'),
3143 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3150 push @$res, { cmd
=> 'rollback' };
3151 push @$res, { cmd
=> 'config' };
3156 __PACKAGE__-
>register_method({
3157 name
=> 'update_snapshot_config',
3158 path
=> '{vmid}/snapshot/{snapname}/config',
3162 description
=> "Update snapshot metadata.",
3164 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3167 additionalProperties
=> 0,
3169 node
=> get_standard_option
('pve-node'),
3170 vmid
=> get_standard_option
('pve-vmid'),
3171 snapname
=> get_standard_option
('pve-snapshot-name'),
3175 description
=> "A textual description or comment.",
3179 returns
=> { type
=> 'null' },
3183 my $rpcenv = PVE
::RPCEnvironment
::get
();
3185 my $authuser = $rpcenv->get_user();
3187 my $vmid = extract_param
($param, 'vmid');
3189 my $snapname = extract_param
($param, 'snapname');
3191 return undef if !defined($param->{description
});
3193 my $updatefn = sub {
3195 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3197 PVE
::QemuConfig-
>check_lock($conf);
3199 my $snap = $conf->{snapshots
}->{$snapname};
3201 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3203 $snap->{description
} = $param->{description
} if defined($param->{description
});
3205 PVE
::QemuConfig-
>write_config($vmid, $conf);
3208 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3213 __PACKAGE__-
>register_method({
3214 name
=> 'get_snapshot_config',
3215 path
=> '{vmid}/snapshot/{snapname}/config',
3218 description
=> "Get snapshot configuration",
3220 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3223 additionalProperties
=> 0,
3225 node
=> get_standard_option
('pve-node'),
3226 vmid
=> get_standard_option
('pve-vmid'),
3227 snapname
=> get_standard_option
('pve-snapshot-name'),
3230 returns
=> { type
=> "object" },
3234 my $rpcenv = PVE
::RPCEnvironment
::get
();
3236 my $authuser = $rpcenv->get_user();
3238 my $vmid = extract_param
($param, 'vmid');
3240 my $snapname = extract_param
($param, 'snapname');
3242 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3244 my $snap = $conf->{snapshots
}->{$snapname};
3246 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3251 __PACKAGE__-
>register_method({
3253 path
=> '{vmid}/snapshot/{snapname}/rollback',
3257 description
=> "Rollback VM state to specified snapshot.",
3259 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3262 additionalProperties
=> 0,
3264 node
=> get_standard_option
('pve-node'),
3265 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3266 snapname
=> get_standard_option
('pve-snapshot-name'),
3271 description
=> "the task ID.",
3276 my $rpcenv = PVE
::RPCEnvironment
::get
();
3278 my $authuser = $rpcenv->get_user();
3280 my $node = extract_param
($param, 'node');
3282 my $vmid = extract_param
($param, 'vmid');
3284 my $snapname = extract_param
($param, 'snapname');
3287 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3288 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3291 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3294 __PACKAGE__-
>register_method({
3295 name
=> 'delsnapshot',
3296 path
=> '{vmid}/snapshot/{snapname}',
3300 description
=> "Delete a VM snapshot.",
3302 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3305 additionalProperties
=> 0,
3307 node
=> get_standard_option
('pve-node'),
3308 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3309 snapname
=> get_standard_option
('pve-snapshot-name'),
3313 description
=> "For removal from config file, even if removing disk snapshots fails.",
3319 description
=> "the task ID.",
3324 my $rpcenv = PVE
::RPCEnvironment
::get
();
3326 my $authuser = $rpcenv->get_user();
3328 my $node = extract_param
($param, 'node');
3330 my $vmid = extract_param
($param, 'vmid');
3332 my $snapname = extract_param
($param, 'snapname');
3335 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3336 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3339 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3342 __PACKAGE__-
>register_method({
3344 path
=> '{vmid}/template',
3348 description
=> "Create a Template.",
3350 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3351 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3354 additionalProperties
=> 0,
3356 node
=> get_standard_option
('pve-node'),
3357 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3361 description
=> "If you want to convert only 1 disk to base image.",
3362 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3367 returns
=> { type
=> 'null'},
3371 my $rpcenv = PVE
::RPCEnvironment
::get
();
3373 my $authuser = $rpcenv->get_user();
3375 my $node = extract_param
($param, 'node');
3377 my $vmid = extract_param
($param, 'vmid');
3379 my $disk = extract_param
($param, 'disk');
3381 my $updatefn = sub {
3383 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3385 PVE
::QemuConfig-
>check_lock($conf);
3387 die "unable to create template, because VM contains snapshots\n"
3388 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3390 die "you can't convert a template to a template\n"
3391 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3393 die "you can't convert a VM to template if VM is running\n"
3394 if PVE
::QemuServer
::check_running
($vmid);
3397 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3400 $conf->{template
} = 1;
3401 PVE
::QemuConfig-
>write_config($vmid, $conf);
3403 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3406 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);