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 PVE
::Storage
::check_volume_access
($rpcenv, $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 PVE
::Storage
::check_volume_access
($rpcenv, $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 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
478 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
482 my $restorefn = sub {
483 my $vmlist = PVE
::Cluster
::get_vmlist
();
484 if ($vmlist->{ids
}->{$vmid}) {
485 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
486 if ($current_node eq $node) {
487 my $conf = PVE
::QemuConfig-
>load_config($vmid);
489 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
491 die "unable to restore vm $vmid - config file already exists\n"
494 die "unable to restore vm $vmid - vm is running\n"
495 if PVE
::QemuServer
::check_running
($vmid);
497 die "unable to restore vm $vmid - vm is a template\n"
498 if PVE
::QemuConfig-
>is_template($conf);
501 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
506 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
509 unique
=> $unique });
511 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
514 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
520 PVE
::Cluster
::check_vmid_unused
($vmid);
530 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
532 # try to be smart about bootdisk
533 my @disks = PVE
::QemuServer
::valid_drive_names
();
535 foreach my $ds (reverse @disks) {
536 next if !$conf->{$ds};
537 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
538 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
542 if (!$conf->{bootdisk
} && $firstdisk) {
543 $conf->{bootdisk
} = $firstdisk;
546 # auto generate uuid if user did not specify smbios1 option
547 if (!$conf->{smbios1
}) {
548 my ($uuid, $uuid_str);
549 UUID
::generate
($uuid);
550 UUID
::unparse
($uuid, $uuid_str);
551 $conf->{smbios1
} = "uuid=$uuid_str";
554 PVE
::QemuConfig-
>write_config($vmid, $conf);
560 foreach my $volid (@$vollist) {
561 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
564 die "create failed - $err";
567 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
570 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
573 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
576 __PACKAGE__-
>register_method({
581 description
=> "Directory index",
586 additionalProperties
=> 0,
588 node
=> get_standard_option
('pve-node'),
589 vmid
=> get_standard_option
('pve-vmid'),
597 subdir
=> { type
=> 'string' },
600 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
606 { subdir
=> 'config' },
607 { subdir
=> 'pending' },
608 { subdir
=> 'status' },
609 { subdir
=> 'unlink' },
610 { subdir
=> 'vncproxy' },
611 { subdir
=> 'migrate' },
612 { subdir
=> 'resize' },
613 { subdir
=> 'move' },
615 { subdir
=> 'rrddata' },
616 { subdir
=> 'monitor' },
617 { subdir
=> 'agent' },
618 { subdir
=> 'snapshot' },
619 { subdir
=> 'spiceproxy' },
620 { subdir
=> 'sendkey' },
621 { subdir
=> 'firewall' },
627 __PACKAGE__-
>register_method ({
628 subclass
=> "PVE::API2::Firewall::VM",
629 path
=> '{vmid}/firewall',
632 __PACKAGE__-
>register_method({
634 path
=> '{vmid}/rrd',
636 protected
=> 1, # fixme: can we avoid that?
638 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
640 description
=> "Read VM RRD statistics (returns PNG)",
642 additionalProperties
=> 0,
644 node
=> get_standard_option
('pve-node'),
645 vmid
=> get_standard_option
('pve-vmid'),
647 description
=> "Specify the time frame you are interested in.",
649 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
652 description
=> "The list of datasources you want to display.",
653 type
=> 'string', format
=> 'pve-configid-list',
656 description
=> "The RRD consolidation function",
658 enum
=> [ 'AVERAGE', 'MAX' ],
666 filename
=> { type
=> 'string' },
672 return PVE
::Cluster
::create_rrd_graph
(
673 "pve2-vm/$param->{vmid}", $param->{timeframe
},
674 $param->{ds
}, $param->{cf
});
678 __PACKAGE__-
>register_method({
680 path
=> '{vmid}/rrddata',
682 protected
=> 1, # fixme: can we avoid that?
684 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
686 description
=> "Read VM RRD statistics",
688 additionalProperties
=> 0,
690 node
=> get_standard_option
('pve-node'),
691 vmid
=> get_standard_option
('pve-vmid'),
693 description
=> "Specify the time frame you are interested in.",
695 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
698 description
=> "The RRD consolidation function",
700 enum
=> [ 'AVERAGE', 'MAX' ],
715 return PVE
::Cluster
::create_rrd_data
(
716 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
720 __PACKAGE__-
>register_method({
722 path
=> '{vmid}/config',
725 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
727 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
730 additionalProperties
=> 0,
732 node
=> get_standard_option
('pve-node'),
733 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
735 description
=> "Get current values (instead of pending values).",
747 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
754 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
756 delete $conf->{snapshots
};
758 if (!$param->{current
}) {
759 foreach my $opt (keys %{$conf->{pending
}}) {
760 next if $opt eq 'delete';
761 my $value = $conf->{pending
}->{$opt};
762 next if ref($value); # just to be sure
763 $conf->{$opt} = $value;
765 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
766 foreach my $opt (keys %$pending_delete_hash) {
767 delete $conf->{$opt} if $conf->{$opt};
771 delete $conf->{pending
};
776 __PACKAGE__-
>register_method({
777 name
=> 'vm_pending',
778 path
=> '{vmid}/pending',
781 description
=> "Get virtual machine configuration, including pending changes.",
783 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
786 additionalProperties
=> 0,
788 node
=> get_standard_option
('pve-node'),
789 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
798 description
=> "Configuration option name.",
802 description
=> "Current value.",
807 description
=> "Pending value.",
812 description
=> "Indicates a pending delete request if present and not 0. " .
813 "The value 2 indicates a force-delete request.",
825 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
827 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
831 foreach my $opt (keys %$conf) {
832 next if ref($conf->{$opt});
833 my $item = { key
=> $opt };
834 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
835 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
836 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
840 foreach my $opt (keys %{$conf->{pending
}}) {
841 next if $opt eq 'delete';
842 next if ref($conf->{pending
}->{$opt}); # just to be sure
843 next if defined($conf->{$opt});
844 my $item = { key
=> $opt };
845 $item->{pending
} = $conf->{pending
}->{$opt};
849 while (my ($opt, $force) = each %$pending_delete_hash) {
850 next if $conf->{pending
}->{$opt}; # just to be sure
851 next if $conf->{$opt};
852 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
859 # POST/PUT {vmid}/config implementation
861 # The original API used PUT (idempotent) an we assumed that all operations
862 # are fast. But it turned out that almost any configuration change can
863 # involve hot-plug actions, or disk alloc/free. Such actions can take long
864 # time to complete and have side effects (not idempotent).
866 # The new implementation uses POST and forks a worker process. We added
867 # a new option 'background_delay'. If specified we wait up to
868 # 'background_delay' second for the worker task to complete. It returns null
869 # if the task is finished within that time, else we return the UPID.
871 my $update_vm_api = sub {
872 my ($param, $sync) = @_;
874 my $rpcenv = PVE
::RPCEnvironment
::get
();
876 my $authuser = $rpcenv->get_user();
878 my $node = extract_param
($param, 'node');
880 my $vmid = extract_param
($param, 'vmid');
882 my $digest = extract_param
($param, 'digest');
884 my $background_delay = extract_param
($param, 'background_delay');
886 my @paramarr = (); # used for log message
887 foreach my $key (keys %$param) {
888 push @paramarr, "-$key", $param->{$key};
891 my $skiplock = extract_param
($param, 'skiplock');
892 raise_param_exc
({ skiplock
=> "Only root may use this option." })
893 if $skiplock && $authuser ne 'root@pam';
895 my $delete_str = extract_param
($param, 'delete');
897 my $revert_str = extract_param
($param, 'revert');
899 my $force = extract_param
($param, 'force');
901 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
903 my $storecfg = PVE
::Storage
::config
();
905 my $defaults = PVE
::QemuServer
::load_defaults
();
907 &$resolve_cdrom_alias($param);
909 # now try to verify all parameters
912 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
913 if (!PVE
::QemuServer
::option_exists
($opt)) {
914 raise_param_exc
({ revert
=> "unknown option '$opt'" });
917 raise_param_exc
({ delete => "you can't use '-$opt' and " .
918 "-revert $opt' at the same time" })
919 if defined($param->{$opt});
925 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
926 $opt = 'ide2' if $opt eq 'cdrom';
928 raise_param_exc
({ delete => "you can't use '-$opt' and " .
929 "-delete $opt' at the same time" })
930 if defined($param->{$opt});
932 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
933 "-revert $opt' at the same time" })
936 if (!PVE
::QemuServer
::option_exists
($opt)) {
937 raise_param_exc
({ delete => "unknown option '$opt'" });
943 foreach my $opt (keys %$param) {
944 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
946 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
947 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
948 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
949 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
950 } elsif ($opt =~ m/^net(\d+)$/) {
952 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
953 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
957 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
959 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
961 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
965 my $conf = PVE
::QemuConfig-
>load_config($vmid);
967 die "checksum missmatch (file change by other user?)\n"
968 if $digest && $digest ne $conf->{digest
};
970 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
972 foreach my $opt (keys %$revert) {
973 if (defined($conf->{$opt})) {
974 $param->{$opt} = $conf->{$opt};
975 } elsif (defined($conf->{pending
}->{$opt})) {
980 if ($param->{memory
} || defined($param->{balloon
})) {
981 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
982 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
984 die "balloon value too large (must be smaller than assigned memory)\n"
985 if $balloon && $balloon > $maxmem;
988 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
992 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
994 # write updates to pending section
996 my $modified = {}; # record what $option we modify
998 foreach my $opt (@delete) {
999 $modified->{$opt} = 1;
1000 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1001 if ($opt =~ m/^unused/) {
1002 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1003 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1004 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1005 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1006 delete $conf->{$opt};
1007 PVE
::QemuConfig-
>write_config($vmid, $conf);
1009 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1010 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1011 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1012 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1013 if defined($conf->{pending
}->{$opt});
1014 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1015 PVE
::QemuConfig-
>write_config($vmid, $conf);
1017 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1018 PVE
::QemuConfig-
>write_config($vmid, $conf);
1022 foreach my $opt (keys %$param) { # add/change
1023 $modified->{$opt} = 1;
1024 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1025 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1027 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1028 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1029 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1030 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1032 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1034 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1035 if defined($conf->{pending
}->{$opt});
1037 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1039 $conf->{pending
}->{$opt} = $param->{$opt};
1041 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1042 PVE
::QemuConfig-
>write_config($vmid, $conf);
1045 # remove pending changes when nothing changed
1046 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1047 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1048 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1050 return if !scalar(keys %{$conf->{pending
}});
1052 my $running = PVE
::QemuServer
::check_running
($vmid);
1054 # apply pending changes
1056 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1060 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1061 raise_param_exc
($errors) if scalar(keys %$errors);
1063 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1073 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1075 if ($background_delay) {
1077 # Note: It would be better to do that in the Event based HTTPServer
1078 # to avoid blocking call to sleep.
1080 my $end_time = time() + $background_delay;
1082 my $task = PVE
::Tools
::upid_decode
($upid);
1085 while (time() < $end_time) {
1086 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1088 sleep(1); # this gets interrupted when child process ends
1092 my $status = PVE
::Tools
::upid_read_status
($upid);
1093 return undef if $status eq 'OK';
1102 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1105 my $vm_config_perm_list = [
1110 'VM.Config.Network',
1112 'VM.Config.Options',
1115 __PACKAGE__-
>register_method({
1116 name
=> 'update_vm_async',
1117 path
=> '{vmid}/config',
1121 description
=> "Set virtual machine options (asynchrounous API).",
1123 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1126 additionalProperties
=> 0,
1127 properties
=> PVE
::QemuServer
::json_config_properties
(
1129 node
=> get_standard_option
('pve-node'),
1130 vmid
=> get_standard_option
('pve-vmid'),
1131 skiplock
=> get_standard_option
('skiplock'),
1133 type
=> 'string', format
=> 'pve-configid-list',
1134 description
=> "A list of settings you want to delete.",
1138 type
=> 'string', format
=> 'pve-configid-list',
1139 description
=> "Revert a pending change.",
1144 description
=> $opt_force_description,
1146 requires
=> 'delete',
1150 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1154 background_delay
=> {
1156 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1167 code
=> $update_vm_api,
1170 __PACKAGE__-
>register_method({
1171 name
=> 'update_vm',
1172 path
=> '{vmid}/config',
1176 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1178 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1181 additionalProperties
=> 0,
1182 properties
=> PVE
::QemuServer
::json_config_properties
(
1184 node
=> get_standard_option
('pve-node'),
1185 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1186 skiplock
=> get_standard_option
('skiplock'),
1188 type
=> 'string', format
=> 'pve-configid-list',
1189 description
=> "A list of settings you want to delete.",
1193 type
=> 'string', format
=> 'pve-configid-list',
1194 description
=> "Revert a pending change.",
1199 description
=> $opt_force_description,
1201 requires
=> 'delete',
1205 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1211 returns
=> { type
=> 'null' },
1214 &$update_vm_api($param, 1);
1220 __PACKAGE__-
>register_method({
1221 name
=> 'destroy_vm',
1226 description
=> "Destroy the vm (also delete all used/owned volumes).",
1228 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1231 additionalProperties
=> 0,
1233 node
=> get_standard_option
('pve-node'),
1234 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1235 skiplock
=> get_standard_option
('skiplock'),
1244 my $rpcenv = PVE
::RPCEnvironment
::get
();
1246 my $authuser = $rpcenv->get_user();
1248 my $vmid = $param->{vmid
};
1250 my $skiplock = $param->{skiplock
};
1251 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1252 if $skiplock && $authuser ne 'root@pam';
1255 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1257 my $storecfg = PVE
::Storage
::config
();
1259 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1261 die "unable to remove VM $vmid - used in HA resources\n"
1262 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1264 # early tests (repeat after locking)
1265 die "VM $vmid is running - destroy failed\n"
1266 if PVE
::QemuServer
::check_running
($vmid);
1271 syslog
('info', "destroy VM $vmid: $upid\n");
1273 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1275 PVE
::AccessControl
::remove_vm_access
($vmid);
1277 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1280 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1283 __PACKAGE__-
>register_method({
1285 path
=> '{vmid}/unlink',
1289 description
=> "Unlink/delete disk images.",
1291 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1294 additionalProperties
=> 0,
1296 node
=> get_standard_option
('pve-node'),
1297 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1299 type
=> 'string', format
=> 'pve-configid-list',
1300 description
=> "A list of disk IDs you want to delete.",
1304 description
=> $opt_force_description,
1309 returns
=> { type
=> 'null'},
1313 $param->{delete} = extract_param
($param, 'idlist');
1315 __PACKAGE__-
>update_vm($param);
1322 __PACKAGE__-
>register_method({
1324 path
=> '{vmid}/vncproxy',
1328 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1330 description
=> "Creates a TCP VNC proxy connections.",
1332 additionalProperties
=> 0,
1334 node
=> get_standard_option
('pve-node'),
1335 vmid
=> get_standard_option
('pve-vmid'),
1339 description
=> "starts websockify instead of vncproxy",
1344 additionalProperties
=> 0,
1346 user
=> { type
=> 'string' },
1347 ticket
=> { type
=> 'string' },
1348 cert
=> { type
=> 'string' },
1349 port
=> { type
=> 'integer' },
1350 upid
=> { type
=> 'string' },
1356 my $rpcenv = PVE
::RPCEnvironment
::get
();
1358 my $authuser = $rpcenv->get_user();
1360 my $vmid = $param->{vmid
};
1361 my $node = $param->{node
};
1362 my $websocket = $param->{websocket
};
1364 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1366 my $authpath = "/vms/$vmid";
1368 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1370 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1373 my ($remip, $family);
1376 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1377 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1378 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1379 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1381 $family = PVE
::Tools
::get_host_address_family
($node);
1384 my $port = PVE
::Tools
::next_vnc_port
($family);
1391 syslog
('info', "starting vnc proxy $upid\n");
1395 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1397 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1399 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1400 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1401 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1402 '-timeout', $timeout, '-authpath', $authpath,
1403 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1406 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1408 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1410 my $qmstr = join(' ', @$qmcmd);
1412 # also redirect stderr (else we get RFB protocol errors)
1413 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1416 PVE
::Tools
::run_command
($cmd);
1421 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1423 PVE
::Tools
::wait_for_vnc_port
($port);
1434 __PACKAGE__-
>register_method({
1435 name
=> 'vncwebsocket',
1436 path
=> '{vmid}/vncwebsocket',
1439 description
=> "You also need to pass a valid ticket (vncticket).",
1440 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1442 description
=> "Opens a weksocket for VNC traffic.",
1444 additionalProperties
=> 0,
1446 node
=> get_standard_option
('pve-node'),
1447 vmid
=> get_standard_option
('pve-vmid'),
1449 description
=> "Ticket from previous call to vncproxy.",
1454 description
=> "Port number returned by previous vncproxy call.",
1464 port
=> { type
=> 'string' },
1470 my $rpcenv = PVE
::RPCEnvironment
::get
();
1472 my $authuser = $rpcenv->get_user();
1474 my $vmid = $param->{vmid
};
1475 my $node = $param->{node
};
1477 my $authpath = "/vms/$vmid";
1479 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1481 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1483 # Note: VNC ports are acessible from outside, so we do not gain any
1484 # security if we verify that $param->{port} belongs to VM $vmid. This
1485 # check is done by verifying the VNC ticket (inside VNC protocol).
1487 my $port = $param->{port
};
1489 return { port
=> $port };
1492 __PACKAGE__-
>register_method({
1493 name
=> 'spiceproxy',
1494 path
=> '{vmid}/spiceproxy',
1499 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1501 description
=> "Returns a SPICE configuration to connect to the VM.",
1503 additionalProperties
=> 0,
1505 node
=> get_standard_option
('pve-node'),
1506 vmid
=> get_standard_option
('pve-vmid'),
1507 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1510 returns
=> get_standard_option
('remote-viewer-config'),
1514 my $rpcenv = PVE
::RPCEnvironment
::get
();
1516 my $authuser = $rpcenv->get_user();
1518 my $vmid = $param->{vmid
};
1519 my $node = $param->{node
};
1520 my $proxy = $param->{proxy
};
1522 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1523 my $title = "VM $vmid";
1524 $title .= " - ". $conf->{name
} if $conf->{name
};
1526 my $port = PVE
::QemuServer
::spice_port
($vmid);
1528 my ($ticket, undef, $remote_viewer_config) =
1529 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1531 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1532 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1534 return $remote_viewer_config;
1537 __PACKAGE__-
>register_method({
1539 path
=> '{vmid}/status',
1542 description
=> "Directory index",
1547 additionalProperties
=> 0,
1549 node
=> get_standard_option
('pve-node'),
1550 vmid
=> get_standard_option
('pve-vmid'),
1558 subdir
=> { type
=> 'string' },
1561 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1567 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1570 { subdir
=> 'current' },
1571 { subdir
=> 'start' },
1572 { subdir
=> 'stop' },
1578 __PACKAGE__-
>register_method({
1579 name
=> 'vm_status',
1580 path
=> '{vmid}/status/current',
1583 protected
=> 1, # qemu pid files are only readable by root
1584 description
=> "Get virtual machine status.",
1586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1589 additionalProperties
=> 0,
1591 node
=> get_standard_option
('pve-node'),
1592 vmid
=> get_standard_option
('pve-vmid'),
1595 returns
=> { type
=> 'object' },
1600 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1602 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1603 my $status = $vmstatus->{$param->{vmid
}};
1605 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1607 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1612 __PACKAGE__-
>register_method({
1614 path
=> '{vmid}/status/start',
1618 description
=> "Start virtual machine.",
1620 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1623 additionalProperties
=> 0,
1625 node
=> get_standard_option
('pve-node'),
1626 vmid
=> get_standard_option
('pve-vmid',
1627 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1628 skiplock
=> get_standard_option
('skiplock'),
1629 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1630 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1633 enum
=> ['secure', 'insecure'],
1634 description
=> "Migration traffic is encrypted using an SSH " .
1635 "tunnel by default. On secure, completely private networks " .
1636 "this can be disabled to increase performance.",
1639 migration_network
=> {
1640 type
=> 'string', format
=> 'CIDR',
1641 description
=> "CIDR of the (sub) network that is used for migration.",
1644 machine
=> get_standard_option
('pve-qm-machine'),
1646 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1658 my $rpcenv = PVE
::RPCEnvironment
::get
();
1660 my $authuser = $rpcenv->get_user();
1662 my $node = extract_param
($param, 'node');
1664 my $vmid = extract_param
($param, 'vmid');
1666 my $machine = extract_param
($param, 'machine');
1668 my $stateuri = extract_param
($param, 'stateuri');
1669 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1670 if $stateuri && $authuser ne 'root@pam';
1672 my $skiplock = extract_param
($param, 'skiplock');
1673 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1674 if $skiplock && $authuser ne 'root@pam';
1676 my $migratedfrom = extract_param
($param, 'migratedfrom');
1677 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1678 if $migratedfrom && $authuser ne 'root@pam';
1680 my $migration_type = extract_param
($param, 'migration_type');
1681 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1682 if $migration_type && $authuser ne 'root@pam';
1684 my $migration_network = extract_param
($param, 'migration_network');
1685 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1686 if $migration_network && $authuser ne 'root@pam';
1688 my $targetstorage = extract_param
($param, 'targetstorage');
1689 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1690 if $targetstorage && $authuser ne 'root@pam';
1692 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1693 if $targetstorage && !$migratedfrom;
1695 # read spice ticket from STDIN
1697 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1698 if (defined(my $line = <>)) {
1700 $spice_ticket = $line;
1704 PVE
::Cluster
::check_cfs_quorum
();
1706 my $storecfg = PVE
::Storage
::config
();
1708 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1709 $rpcenv->{type
} ne 'ha') {
1714 my $service = "vm:$vmid";
1716 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1718 print "Executing HA start for VM $vmid\n";
1720 PVE
::Tools
::run_command
($cmd);
1725 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1732 syslog
('info', "start VM $vmid: $upid\n");
1734 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1735 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1740 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1744 __PACKAGE__-
>register_method({
1746 path
=> '{vmid}/status/stop',
1750 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1751 "is akin to pulling the power plug of a running computer and may damage the VM data",
1753 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1756 additionalProperties
=> 0,
1758 node
=> get_standard_option
('pve-node'),
1759 vmid
=> get_standard_option
('pve-vmid',
1760 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1761 skiplock
=> get_standard_option
('skiplock'),
1762 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1764 description
=> "Wait maximal timeout seconds.",
1770 description
=> "Do not deactivate storage volumes.",
1783 my $rpcenv = PVE
::RPCEnvironment
::get
();
1785 my $authuser = $rpcenv->get_user();
1787 my $node = extract_param
($param, 'node');
1789 my $vmid = extract_param
($param, 'vmid');
1791 my $skiplock = extract_param
($param, 'skiplock');
1792 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1793 if $skiplock && $authuser ne 'root@pam';
1795 my $keepActive = extract_param
($param, 'keepActive');
1796 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1797 if $keepActive && $authuser ne 'root@pam';
1799 my $migratedfrom = extract_param
($param, 'migratedfrom');
1800 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1801 if $migratedfrom && $authuser ne 'root@pam';
1804 my $storecfg = PVE
::Storage
::config
();
1806 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1811 my $service = "vm:$vmid";
1813 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1815 print "Executing HA stop for VM $vmid\n";
1817 PVE
::Tools
::run_command
($cmd);
1822 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1828 syslog
('info', "stop VM $vmid: $upid\n");
1830 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1831 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1836 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1840 __PACKAGE__-
>register_method({
1842 path
=> '{vmid}/status/reset',
1846 description
=> "Reset virtual machine.",
1848 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1851 additionalProperties
=> 0,
1853 node
=> get_standard_option
('pve-node'),
1854 vmid
=> get_standard_option
('pve-vmid',
1855 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1856 skiplock
=> get_standard_option
('skiplock'),
1865 my $rpcenv = PVE
::RPCEnvironment
::get
();
1867 my $authuser = $rpcenv->get_user();
1869 my $node = extract_param
($param, 'node');
1871 my $vmid = extract_param
($param, 'vmid');
1873 my $skiplock = extract_param
($param, 'skiplock');
1874 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1875 if $skiplock && $authuser ne 'root@pam';
1877 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1882 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1887 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1890 __PACKAGE__-
>register_method({
1891 name
=> 'vm_shutdown',
1892 path
=> '{vmid}/status/shutdown',
1896 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1897 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1899 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1902 additionalProperties
=> 0,
1904 node
=> get_standard_option
('pve-node'),
1905 vmid
=> get_standard_option
('pve-vmid',
1906 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1907 skiplock
=> get_standard_option
('skiplock'),
1909 description
=> "Wait maximal timeout seconds.",
1915 description
=> "Make sure the VM stops.",
1921 description
=> "Do not deactivate storage volumes.",
1934 my $rpcenv = PVE
::RPCEnvironment
::get
();
1936 my $authuser = $rpcenv->get_user();
1938 my $node = extract_param
($param, 'node');
1940 my $vmid = extract_param
($param, 'vmid');
1942 my $skiplock = extract_param
($param, 'skiplock');
1943 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1944 if $skiplock && $authuser ne 'root@pam';
1946 my $keepActive = extract_param
($param, 'keepActive');
1947 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1948 if $keepActive && $authuser ne 'root@pam';
1950 my $storecfg = PVE
::Storage
::config
();
1954 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1955 # otherwise, we will infer a shutdown command, but run into the timeout,
1956 # then when the vm is resumed, it will instantly shutdown
1958 # checking the qmp status here to get feedback to the gui/cli/api
1959 # and the status query should not take too long
1962 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1966 if (!$err && $qmpstatus->{status
} eq "paused") {
1967 if ($param->{forceStop
}) {
1968 warn "VM is paused - stop instead of shutdown\n";
1971 die "VM is paused - cannot shutdown\n";
1975 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
1976 ($rpcenv->{type
} ne 'ha')) {
1981 my $service = "vm:$vmid";
1983 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1985 print "Executing HA stop for VM $vmid\n";
1987 PVE
::Tools
::run_command
($cmd);
1992 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1999 syslog
('info', "shutdown VM $vmid: $upid\n");
2001 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2002 $shutdown, $param->{forceStop
}, $keepActive);
2007 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2011 __PACKAGE__-
>register_method({
2012 name
=> 'vm_suspend',
2013 path
=> '{vmid}/status/suspend',
2017 description
=> "Suspend virtual machine.",
2019 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2022 additionalProperties
=> 0,
2024 node
=> get_standard_option
('pve-node'),
2025 vmid
=> get_standard_option
('pve-vmid',
2026 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2027 skiplock
=> get_standard_option
('skiplock'),
2036 my $rpcenv = PVE
::RPCEnvironment
::get
();
2038 my $authuser = $rpcenv->get_user();
2040 my $node = extract_param
($param, 'node');
2042 my $vmid = extract_param
($param, 'vmid');
2044 my $skiplock = extract_param
($param, 'skiplock');
2045 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2046 if $skiplock && $authuser ne 'root@pam';
2048 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2053 syslog
('info', "suspend VM $vmid: $upid\n");
2055 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2060 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2063 __PACKAGE__-
>register_method({
2064 name
=> 'vm_resume',
2065 path
=> '{vmid}/status/resume',
2069 description
=> "Resume virtual machine.",
2071 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2074 additionalProperties
=> 0,
2076 node
=> get_standard_option
('pve-node'),
2077 vmid
=> get_standard_option
('pve-vmid',
2078 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2079 skiplock
=> get_standard_option
('skiplock'),
2080 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2090 my $rpcenv = PVE
::RPCEnvironment
::get
();
2092 my $authuser = $rpcenv->get_user();
2094 my $node = extract_param
($param, 'node');
2096 my $vmid = extract_param
($param, 'vmid');
2098 my $skiplock = extract_param
($param, 'skiplock');
2099 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2100 if $skiplock && $authuser ne 'root@pam';
2102 my $nocheck = extract_param
($param, 'nocheck');
2104 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2109 syslog
('info', "resume VM $vmid: $upid\n");
2111 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2116 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2119 __PACKAGE__-
>register_method({
2120 name
=> 'vm_sendkey',
2121 path
=> '{vmid}/sendkey',
2125 description
=> "Send key event to virtual machine.",
2127 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2130 additionalProperties
=> 0,
2132 node
=> get_standard_option
('pve-node'),
2133 vmid
=> get_standard_option
('pve-vmid',
2134 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2135 skiplock
=> get_standard_option
('skiplock'),
2137 description
=> "The key (qemu monitor encoding).",
2142 returns
=> { type
=> 'null'},
2146 my $rpcenv = PVE
::RPCEnvironment
::get
();
2148 my $authuser = $rpcenv->get_user();
2150 my $node = extract_param
($param, 'node');
2152 my $vmid = extract_param
($param, 'vmid');
2154 my $skiplock = extract_param
($param, 'skiplock');
2155 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2156 if $skiplock && $authuser ne 'root@pam';
2158 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2163 __PACKAGE__-
>register_method({
2164 name
=> 'vm_feature',
2165 path
=> '{vmid}/feature',
2169 description
=> "Check if feature for virtual machine is available.",
2171 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2174 additionalProperties
=> 0,
2176 node
=> get_standard_option
('pve-node'),
2177 vmid
=> get_standard_option
('pve-vmid'),
2179 description
=> "Feature to check.",
2181 enum
=> [ 'snapshot', 'clone', 'copy' ],
2183 snapname
=> get_standard_option
('pve-snapshot-name', {
2191 hasFeature
=> { type
=> 'boolean' },
2194 items
=> { type
=> 'string' },
2201 my $node = extract_param
($param, 'node');
2203 my $vmid = extract_param
($param, 'vmid');
2205 my $snapname = extract_param
($param, 'snapname');
2207 my $feature = extract_param
($param, 'feature');
2209 my $running = PVE
::QemuServer
::check_running
($vmid);
2211 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2214 my $snap = $conf->{snapshots
}->{$snapname};
2215 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2218 my $storecfg = PVE
::Storage
::config
();
2220 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2221 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2224 hasFeature
=> $hasFeature,
2225 nodes
=> [ keys %$nodelist ],
2229 __PACKAGE__-
>register_method({
2231 path
=> '{vmid}/clone',
2235 description
=> "Create a copy of virtual machine/template.",
2237 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2238 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2239 "'Datastore.AllocateSpace' on any used storage.",
2242 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2244 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2245 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2250 additionalProperties
=> 0,
2252 node
=> get_standard_option
('pve-node'),
2253 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2254 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2257 type
=> 'string', format
=> 'dns-name',
2258 description
=> "Set a name for the new VM.",
2263 description
=> "Description for the new VM.",
2267 type
=> 'string', format
=> 'pve-poolid',
2268 description
=> "Add the new VM to the specified pool.",
2270 snapname
=> get_standard_option
('pve-snapshot-name', {
2273 storage
=> get_standard_option
('pve-storage-id', {
2274 description
=> "Target storage for full clone.",
2279 description
=> "Target format for file storage.",
2283 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2288 description
=> "Create a full copy of all disk. This is always done when " .
2289 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2292 target
=> get_standard_option
('pve-node', {
2293 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2304 my $rpcenv = PVE
::RPCEnvironment
::get
();
2306 my $authuser = $rpcenv->get_user();
2308 my $node = extract_param
($param, 'node');
2310 my $vmid = extract_param
($param, 'vmid');
2312 my $newid = extract_param
($param, 'newid');
2314 my $pool = extract_param
($param, 'pool');
2316 if (defined($pool)) {
2317 $rpcenv->check_pool_exist($pool);
2320 my $snapname = extract_param
($param, 'snapname');
2322 my $storage = extract_param
($param, 'storage');
2324 my $format = extract_param
($param, 'format');
2326 my $target = extract_param
($param, 'target');
2328 my $localnode = PVE
::INotify
::nodename
();
2330 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2332 PVE
::Cluster
::check_node_exists
($target) if $target;
2334 my $storecfg = PVE
::Storage
::config
();
2337 # check if storage is enabled on local node
2338 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2340 # check if storage is available on target node
2341 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2342 # clone only works if target storage is shared
2343 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2344 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2348 PVE
::Cluster
::check_cfs_quorum
();
2350 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2352 # exclusive lock if VM is running - else shared lock is enough;
2353 my $shared_lock = $running ?
0 : 1;
2357 # do all tests after lock
2358 # we also try to do all tests before we fork the worker
2360 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2362 PVE
::QemuConfig-
>check_lock($conf);
2364 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2366 die "unexpected state change\n" if $verify_running != $running;
2368 die "snapshot '$snapname' does not exist\n"
2369 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2371 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2373 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2375 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2377 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2379 die "unable to create VM $newid: config file already exists\n"
2382 my $newconf = { lock => 'clone' };
2387 foreach my $opt (keys %$oldconf) {
2388 my $value = $oldconf->{$opt};
2390 # do not copy snapshot related info
2391 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2392 $opt eq 'vmstate' || $opt eq 'snapstate';
2394 # no need to copy unused images, because VMID(owner) changes anyways
2395 next if $opt =~ m/^unused\d+$/;
2397 # always change MAC! address
2398 if ($opt =~ m/^net(\d+)$/) {
2399 my $net = PVE
::QemuServer
::parse_net
($value);
2400 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2401 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2402 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2403 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2404 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2405 die "unable to parse drive options for '$opt'\n" if !$drive;
2406 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2407 $newconf->{$opt} = $value; # simply copy configuration
2409 if ($param->{full
}) {
2410 die "Full clone feature is not available"
2411 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2412 $fullclone->{$opt} = 1;
2414 # not full means clone instead of copy
2415 die "Linked clone feature is not available"
2416 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2418 $drives->{$opt} = $drive;
2419 push @$vollist, $drive->{file
};
2422 # copy everything else
2423 $newconf->{$opt} = $value;
2427 # auto generate a new uuid
2428 my ($uuid, $uuid_str);
2429 UUID
::generate
($uuid);
2430 UUID
::unparse
($uuid, $uuid_str);
2431 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2432 $smbios1->{uuid
} = $uuid_str;
2433 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2435 delete $newconf->{template
};
2437 if ($param->{name
}) {
2438 $newconf->{name
} = $param->{name
};
2440 if ($oldconf->{name
}) {
2441 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2443 $newconf->{name
} = "Copy-of-VM-$vmid";
2447 if ($param->{description
}) {
2448 $newconf->{description
} = $param->{description
};
2451 # create empty/temp config - this fails if VM already exists on other node
2452 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2457 my $newvollist = [];
2461 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2463 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2465 my $total_jobs = scalar(keys %{$drives});
2468 foreach my $opt (keys %$drives) {
2469 my $drive = $drives->{$opt};
2470 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2472 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2473 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2474 $jobs, $skipcomplete, $oldconf->{agent
});
2476 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2478 PVE
::QemuConfig-
>write_config($newid, $newconf);
2482 delete $newconf->{lock};
2483 PVE
::QemuConfig-
>write_config($newid, $newconf);
2486 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2487 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2488 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2490 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2491 die "Failed to move config to node '$target' - rename failed: $!\n"
2492 if !rename($conffile, $newconffile);
2495 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2500 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2502 sleep 1; # some storage like rbd need to wait before release volume - really?
2504 foreach my $volid (@$newvollist) {
2505 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2508 die "clone failed: $err";
2514 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2516 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2519 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2520 # Aquire exclusive lock lock for $newid
2521 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2526 __PACKAGE__-
>register_method({
2527 name
=> 'move_vm_disk',
2528 path
=> '{vmid}/move_disk',
2532 description
=> "Move volume to different storage.",
2534 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2536 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2537 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2541 additionalProperties
=> 0,
2543 node
=> get_standard_option
('pve-node'),
2544 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2547 description
=> "The disk you want to move.",
2548 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2550 storage
=> get_standard_option
('pve-storage-id', {
2551 description
=> "Target storage.",
2552 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2556 description
=> "Target Format.",
2557 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2562 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2568 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2576 description
=> "the task ID.",
2581 my $rpcenv = PVE
::RPCEnvironment
::get
();
2583 my $authuser = $rpcenv->get_user();
2585 my $node = extract_param
($param, 'node');
2587 my $vmid = extract_param
($param, 'vmid');
2589 my $digest = extract_param
($param, 'digest');
2591 my $disk = extract_param
($param, 'disk');
2593 my $storeid = extract_param
($param, 'storage');
2595 my $format = extract_param
($param, 'format');
2597 my $storecfg = PVE
::Storage
::config
();
2599 my $updatefn = sub {
2601 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2603 PVE
::QemuConfig-
>check_lock($conf);
2605 die "checksum missmatch (file change by other user?)\n"
2606 if $digest && $digest ne $conf->{digest
};
2608 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2610 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2612 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2614 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2617 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2618 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2622 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2623 (!$format || !$oldfmt || $oldfmt eq $format);
2625 # this only checks snapshots because $disk is passed!
2626 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2627 die "you can't move a disk with snapshots and delete the source\n"
2628 if $snapshotted && $param->{delete};
2630 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2632 my $running = PVE
::QemuServer
::check_running
($vmid);
2634 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2638 my $newvollist = [];
2641 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2643 warn "moving disk with snapshots, snapshots will not be moved!\n"
2646 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2647 $vmid, $storeid, $format, 1, $newvollist);
2649 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2651 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2653 # convert moved disk to base if part of template
2654 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2655 if PVE
::QemuConfig-
>is_template($conf);
2657 PVE
::QemuConfig-
>write_config($vmid, $conf);
2660 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2661 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2668 foreach my $volid (@$newvollist) {
2669 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2672 die "storage migration failed: $err";
2675 if ($param->{delete}) {
2677 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2678 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2684 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2687 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2690 __PACKAGE__-
>register_method({
2691 name
=> 'migrate_vm',
2692 path
=> '{vmid}/migrate',
2696 description
=> "Migrate virtual machine. Creates a new migration task.",
2698 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2701 additionalProperties
=> 0,
2703 node
=> get_standard_option
('pve-node'),
2704 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2705 target
=> get_standard_option
('pve-node', {
2706 description
=> "Target node.",
2707 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2711 description
=> "Use online/live migration.",
2716 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2721 enum
=> ['secure', 'insecure'],
2722 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2725 migration_network
=> {
2726 type
=> 'string', format
=> 'CIDR',
2727 description
=> "CIDR of the (sub) network that is used for migration.",
2730 "with-local-disks" => {
2732 description
=> "Enable live storage migration for local disk",
2735 targetstorage
=> get_standard_option
('pve-storage-id', {
2736 description
=> "Default target storage.",
2738 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2744 description
=> "the task ID.",
2749 my $rpcenv = PVE
::RPCEnvironment
::get
();
2751 my $authuser = $rpcenv->get_user();
2753 my $target = extract_param
($param, 'target');
2755 my $localnode = PVE
::INotify
::nodename
();
2756 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2758 PVE
::Cluster
::check_cfs_quorum
();
2760 PVE
::Cluster
::check_node_exists
($target);
2762 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2764 my $vmid = extract_param
($param, 'vmid');
2766 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2767 if !$param->{online
} && $param->{targetstorage
};
2769 raise_param_exc
({ force
=> "Only root may use this option." })
2770 if $param->{force
} && $authuser ne 'root@pam';
2772 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2773 if $param->{migration_type
} && $authuser ne 'root@pam';
2775 # allow root only until better network permissions are available
2776 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2777 if $param->{migration_network
} && $authuser ne 'root@pam';
2780 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2782 # try to detect errors early
2784 PVE
::QemuConfig-
>check_lock($conf);
2786 if (PVE
::QemuServer
::check_running
($vmid)) {
2787 die "cant migrate running VM without --online\n"
2788 if !$param->{online
};
2791 my $storecfg = PVE
::Storage
::config
();
2792 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2794 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2799 my $service = "vm:$vmid";
2801 my $cmd = ['ha-manager', 'migrate', $service, $target];
2803 print "Executing HA migrate for VM $vmid to node $target\n";
2805 PVE
::Tools
::run_command
($cmd);
2810 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2817 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2820 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2825 __PACKAGE__-
>register_method({
2827 path
=> '{vmid}/monitor',
2831 description
=> "Execute Qemu monitor commands.",
2833 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2834 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2837 additionalProperties
=> 0,
2839 node
=> get_standard_option
('pve-node'),
2840 vmid
=> get_standard_option
('pve-vmid'),
2843 description
=> "The monitor command.",
2847 returns
=> { type
=> 'string'},
2851 my $rpcenv = PVE
::RPCEnvironment
::get
();
2852 my $authuser = $rpcenv->get_user();
2855 my $command = shift;
2856 return $command =~ m/^\s*info(\s+|$)/
2857 || $command =~ m/^\s*help\s*$/;
2860 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2861 if !&$is_ro($param->{command
});
2863 my $vmid = $param->{vmid
};
2865 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2869 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2871 $res = "ERROR: $@" if $@;
2876 my $guest_agent_commands = [
2884 'network-get-interfaces',
2887 'get-memory-blocks',
2888 'get-memory-block-info',
2895 __PACKAGE__-
>register_method({
2897 path
=> '{vmid}/agent',
2901 description
=> "Execute Qemu Guest Agent commands.",
2903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2906 additionalProperties
=> 0,
2908 node
=> get_standard_option
('pve-node'),
2909 vmid
=> get_standard_option
('pve-vmid', {
2910 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2913 description
=> "The QGA command.",
2914 enum
=> $guest_agent_commands,
2920 description
=> "Returns an object with a single `result` property. The type of that
2921 property depends on the executed command.",
2926 my $vmid = $param->{vmid
};
2928 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2930 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2931 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2933 my $cmd = $param->{command
};
2935 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2937 return { result
=> $res };
2940 __PACKAGE__-
>register_method({
2941 name
=> 'resize_vm',
2942 path
=> '{vmid}/resize',
2946 description
=> "Extend volume size.",
2948 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2951 additionalProperties
=> 0,
2953 node
=> get_standard_option
('pve-node'),
2954 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2955 skiplock
=> get_standard_option
('skiplock'),
2958 description
=> "The disk you want to resize.",
2959 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2963 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2964 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.",
2968 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2974 returns
=> { type
=> 'null'},
2978 my $rpcenv = PVE
::RPCEnvironment
::get
();
2980 my $authuser = $rpcenv->get_user();
2982 my $node = extract_param
($param, 'node');
2984 my $vmid = extract_param
($param, 'vmid');
2986 my $digest = extract_param
($param, 'digest');
2988 my $disk = extract_param
($param, 'disk');
2990 my $sizestr = extract_param
($param, 'size');
2992 my $skiplock = extract_param
($param, 'skiplock');
2993 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2994 if $skiplock && $authuser ne 'root@pam';
2996 my $storecfg = PVE
::Storage
::config
();
2998 my $updatefn = sub {
3000 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3002 die "checksum missmatch (file change by other user?)\n"
3003 if $digest && $digest ne $conf->{digest
};
3004 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3006 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3008 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3010 my (undef, undef, undef, undef, undef, undef, $format) =
3011 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3013 die "can't resize volume: $disk if snapshot exists\n"
3014 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3016 my $volid = $drive->{file
};
3018 die "disk '$disk' has no associated volume\n" if !$volid;
3020 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3022 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3024 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3026 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3027 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3029 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3030 my ($ext, $newsize, $unit) = ($1, $2, $4);
3033 $newsize = $newsize * 1024;
3034 } elsif ($unit eq 'M') {
3035 $newsize = $newsize * 1024 * 1024;
3036 } elsif ($unit eq 'G') {
3037 $newsize = $newsize * 1024 * 1024 * 1024;
3038 } elsif ($unit eq 'T') {
3039 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3042 $newsize += $size if $ext;
3043 $newsize = int($newsize);
3045 die "unable to skrink disk size\n" if $newsize < $size;
3047 return if $size == $newsize;
3049 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3051 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3053 $drive->{size
} = $newsize;
3054 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3056 PVE
::QemuConfig-
>write_config($vmid, $conf);
3059 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3063 __PACKAGE__-
>register_method({
3064 name
=> 'snapshot_list',
3065 path
=> '{vmid}/snapshot',
3067 description
=> "List all snapshots.",
3069 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3072 protected
=> 1, # qemu pid files are only readable by root
3074 additionalProperties
=> 0,
3076 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3077 node
=> get_standard_option
('pve-node'),
3086 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3091 my $vmid = $param->{vmid
};
3093 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3094 my $snaphash = $conf->{snapshots
} || {};
3098 foreach my $name (keys %$snaphash) {
3099 my $d = $snaphash->{$name};
3102 snaptime
=> $d->{snaptime
} || 0,
3103 vmstate
=> $d->{vmstate
} ?
1 : 0,
3104 description
=> $d->{description
} || '',
3106 $item->{parent
} = $d->{parent
} if $d->{parent
};
3107 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3111 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3112 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3113 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3115 push @$res, $current;
3120 __PACKAGE__-
>register_method({
3122 path
=> '{vmid}/snapshot',
3126 description
=> "Snapshot a VM.",
3128 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3131 additionalProperties
=> 0,
3133 node
=> get_standard_option
('pve-node'),
3134 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3135 snapname
=> get_standard_option
('pve-snapshot-name'),
3139 description
=> "Save the vmstate",
3144 description
=> "A textual description or comment.",
3150 description
=> "the task ID.",
3155 my $rpcenv = PVE
::RPCEnvironment
::get
();
3157 my $authuser = $rpcenv->get_user();
3159 my $node = extract_param
($param, 'node');
3161 my $vmid = extract_param
($param, 'vmid');
3163 my $snapname = extract_param
($param, 'snapname');
3165 die "unable to use snapshot name 'current' (reserved name)\n"
3166 if $snapname eq 'current';
3169 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3170 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3171 $param->{description
});
3174 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3177 __PACKAGE__-
>register_method({
3178 name
=> 'snapshot_cmd_idx',
3179 path
=> '{vmid}/snapshot/{snapname}',
3186 additionalProperties
=> 0,
3188 vmid
=> get_standard_option
('pve-vmid'),
3189 node
=> get_standard_option
('pve-node'),
3190 snapname
=> get_standard_option
('pve-snapshot-name'),
3199 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3206 push @$res, { cmd
=> 'rollback' };
3207 push @$res, { cmd
=> 'config' };
3212 __PACKAGE__-
>register_method({
3213 name
=> 'update_snapshot_config',
3214 path
=> '{vmid}/snapshot/{snapname}/config',
3218 description
=> "Update snapshot metadata.",
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'),
3231 description
=> "A textual description or comment.",
3235 returns
=> { type
=> 'null' },
3239 my $rpcenv = PVE
::RPCEnvironment
::get
();
3241 my $authuser = $rpcenv->get_user();
3243 my $vmid = extract_param
($param, 'vmid');
3245 my $snapname = extract_param
($param, 'snapname');
3247 return undef if !defined($param->{description
});
3249 my $updatefn = sub {
3251 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3253 PVE
::QemuConfig-
>check_lock($conf);
3255 my $snap = $conf->{snapshots
}->{$snapname};
3257 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3259 $snap->{description
} = $param->{description
} if defined($param->{description
});
3261 PVE
::QemuConfig-
>write_config($vmid, $conf);
3264 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3269 __PACKAGE__-
>register_method({
3270 name
=> 'get_snapshot_config',
3271 path
=> '{vmid}/snapshot/{snapname}/config',
3274 description
=> "Get snapshot configuration",
3276 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3279 additionalProperties
=> 0,
3281 node
=> get_standard_option
('pve-node'),
3282 vmid
=> get_standard_option
('pve-vmid'),
3283 snapname
=> get_standard_option
('pve-snapshot-name'),
3286 returns
=> { type
=> "object" },
3290 my $rpcenv = PVE
::RPCEnvironment
::get
();
3292 my $authuser = $rpcenv->get_user();
3294 my $vmid = extract_param
($param, 'vmid');
3296 my $snapname = extract_param
($param, 'snapname');
3298 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3300 my $snap = $conf->{snapshots
}->{$snapname};
3302 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3307 __PACKAGE__-
>register_method({
3309 path
=> '{vmid}/snapshot/{snapname}/rollback',
3313 description
=> "Rollback VM state to specified snapshot.",
3315 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3318 additionalProperties
=> 0,
3320 node
=> get_standard_option
('pve-node'),
3321 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3322 snapname
=> get_standard_option
('pve-snapshot-name'),
3327 description
=> "the task ID.",
3332 my $rpcenv = PVE
::RPCEnvironment
::get
();
3334 my $authuser = $rpcenv->get_user();
3336 my $node = extract_param
($param, 'node');
3338 my $vmid = extract_param
($param, 'vmid');
3340 my $snapname = extract_param
($param, 'snapname');
3343 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3344 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3347 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3350 __PACKAGE__-
>register_method({
3351 name
=> 'delsnapshot',
3352 path
=> '{vmid}/snapshot/{snapname}',
3356 description
=> "Delete a VM snapshot.",
3358 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3361 additionalProperties
=> 0,
3363 node
=> get_standard_option
('pve-node'),
3364 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3365 snapname
=> get_standard_option
('pve-snapshot-name'),
3369 description
=> "For removal from config file, even if removing disk snapshots fails.",
3375 description
=> "the task ID.",
3380 my $rpcenv = PVE
::RPCEnvironment
::get
();
3382 my $authuser = $rpcenv->get_user();
3384 my $node = extract_param
($param, 'node');
3386 my $vmid = extract_param
($param, 'vmid');
3388 my $snapname = extract_param
($param, 'snapname');
3391 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3392 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3395 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3398 __PACKAGE__-
>register_method({
3400 path
=> '{vmid}/template',
3404 description
=> "Create a Template.",
3406 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3407 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3410 additionalProperties
=> 0,
3412 node
=> get_standard_option
('pve-node'),
3413 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3417 description
=> "If you want to convert only 1 disk to base image.",
3418 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3423 returns
=> { type
=> 'null'},
3427 my $rpcenv = PVE
::RPCEnvironment
::get
();
3429 my $authuser = $rpcenv->get_user();
3431 my $node = extract_param
($param, 'node');
3433 my $vmid = extract_param
($param, 'vmid');
3435 my $disk = extract_param
($param, 'disk');
3437 my $updatefn = sub {
3439 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3441 PVE
::QemuConfig-
>check_lock($conf);
3443 die "unable to create template, because VM contains snapshots\n"
3444 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3446 die "you can't convert a template to a template\n"
3447 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3449 die "you can't convert a VM to template if VM is running\n"
3450 if PVE
::QemuServer
::check_running
($vmid);
3453 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3456 $conf->{template
} = 1;
3457 PVE
::QemuConfig-
>write_config($vmid, $conf);
3459 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3462 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);