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 PVE
::QemuConfig-
>write_config($vmid, $conf);
2656 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2657 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2664 foreach my $volid (@$newvollist) {
2665 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2668 die "storage migration failed: $err";
2671 if ($param->{delete}) {
2673 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2674 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2680 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2683 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2686 __PACKAGE__-
>register_method({
2687 name
=> 'migrate_vm',
2688 path
=> '{vmid}/migrate',
2692 description
=> "Migrate virtual machine. Creates a new migration task.",
2694 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2697 additionalProperties
=> 0,
2699 node
=> get_standard_option
('pve-node'),
2700 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2701 target
=> get_standard_option
('pve-node', {
2702 description
=> "Target node.",
2703 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2707 description
=> "Use online/live migration.",
2712 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2717 enum
=> ['secure', 'insecure'],
2718 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2721 migration_network
=> {
2722 type
=> 'string', format
=> 'CIDR',
2723 description
=> "CIDR of the (sub) network that is used for migration.",
2726 "with-local-disks" => {
2728 description
=> "Enable live storage migration for local disk",
2731 targetstorage
=> get_standard_option
('pve-storage-id', {
2732 description
=> "Default target storage.",
2734 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2740 description
=> "the task ID.",
2745 my $rpcenv = PVE
::RPCEnvironment
::get
();
2747 my $authuser = $rpcenv->get_user();
2749 my $target = extract_param
($param, 'target');
2751 my $localnode = PVE
::INotify
::nodename
();
2752 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2754 PVE
::Cluster
::check_cfs_quorum
();
2756 PVE
::Cluster
::check_node_exists
($target);
2758 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2760 my $vmid = extract_param
($param, 'vmid');
2762 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2763 if !$param->{online
} && $param->{targetstorage
};
2765 raise_param_exc
({ force
=> "Only root may use this option." })
2766 if $param->{force
} && $authuser ne 'root@pam';
2768 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2769 if $param->{migration_type
} && $authuser ne 'root@pam';
2771 # allow root only until better network permissions are available
2772 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2773 if $param->{migration_network
} && $authuser ne 'root@pam';
2776 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2778 # try to detect errors early
2780 PVE
::QemuConfig-
>check_lock($conf);
2782 if (PVE
::QemuServer
::check_running
($vmid)) {
2783 die "cant migrate running VM without --online\n"
2784 if !$param->{online
};
2787 my $storecfg = PVE
::Storage
::config
();
2788 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2790 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2795 my $service = "vm:$vmid";
2797 my $cmd = ['ha-manager', 'migrate', $service, $target];
2799 print "Executing HA migrate for VM $vmid to node $target\n";
2801 PVE
::Tools
::run_command
($cmd);
2806 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2813 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2816 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2821 __PACKAGE__-
>register_method({
2823 path
=> '{vmid}/monitor',
2827 description
=> "Execute Qemu monitor commands.",
2829 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2830 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2833 additionalProperties
=> 0,
2835 node
=> get_standard_option
('pve-node'),
2836 vmid
=> get_standard_option
('pve-vmid'),
2839 description
=> "The monitor command.",
2843 returns
=> { type
=> 'string'},
2847 my $rpcenv = PVE
::RPCEnvironment
::get
();
2848 my $authuser = $rpcenv->get_user();
2851 my $command = shift;
2852 return $command =~ m/^\s*info(\s+|$)/
2853 || $command =~ m/^\s*help\s*$/;
2856 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2857 if !&$is_ro($param->{command
});
2859 my $vmid = $param->{vmid
};
2861 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2865 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2867 $res = "ERROR: $@" if $@;
2872 my $guest_agent_commands = [
2880 'network-get-interfaces',
2883 'get-memory-blocks',
2884 'get-memory-block-info',
2891 __PACKAGE__-
>register_method({
2893 path
=> '{vmid}/agent',
2897 description
=> "Execute Qemu Guest Agent commands.",
2899 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2902 additionalProperties
=> 0,
2904 node
=> get_standard_option
('pve-node'),
2905 vmid
=> get_standard_option
('pve-vmid', {
2906 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2909 description
=> "The QGA command.",
2910 enum
=> $guest_agent_commands,
2916 description
=> "Returns an object with a single `result` property. The type of that
2917 property depends on the executed command.",
2922 my $vmid = $param->{vmid
};
2924 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2926 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2927 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2929 my $cmd = $param->{command
};
2931 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
2933 return { result
=> $res };
2936 __PACKAGE__-
>register_method({
2937 name
=> 'resize_vm',
2938 path
=> '{vmid}/resize',
2942 description
=> "Extend volume size.",
2944 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2947 additionalProperties
=> 0,
2949 node
=> get_standard_option
('pve-node'),
2950 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2951 skiplock
=> get_standard_option
('skiplock'),
2954 description
=> "The disk you want to resize.",
2955 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2959 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2960 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.",
2964 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2970 returns
=> { type
=> 'null'},
2974 my $rpcenv = PVE
::RPCEnvironment
::get
();
2976 my $authuser = $rpcenv->get_user();
2978 my $node = extract_param
($param, 'node');
2980 my $vmid = extract_param
($param, 'vmid');
2982 my $digest = extract_param
($param, 'digest');
2984 my $disk = extract_param
($param, 'disk');
2986 my $sizestr = extract_param
($param, 'size');
2988 my $skiplock = extract_param
($param, 'skiplock');
2989 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2990 if $skiplock && $authuser ne 'root@pam';
2992 my $storecfg = PVE
::Storage
::config
();
2994 my $updatefn = sub {
2996 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2998 die "checksum missmatch (file change by other user?)\n"
2999 if $digest && $digest ne $conf->{digest
};
3000 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3002 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3004 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3006 my (undef, undef, undef, undef, undef, undef, $format) =
3007 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3009 die "can't resize volume: $disk if snapshot exists\n"
3010 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3012 my $volid = $drive->{file
};
3014 die "disk '$disk' has no associated volume\n" if !$volid;
3016 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3018 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3020 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3022 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3023 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3025 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3026 my ($ext, $newsize, $unit) = ($1, $2, $4);
3029 $newsize = $newsize * 1024;
3030 } elsif ($unit eq 'M') {
3031 $newsize = $newsize * 1024 * 1024;
3032 } elsif ($unit eq 'G') {
3033 $newsize = $newsize * 1024 * 1024 * 1024;
3034 } elsif ($unit eq 'T') {
3035 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3038 $newsize += $size if $ext;
3039 $newsize = int($newsize);
3041 die "unable to skrink disk size\n" if $newsize < $size;
3043 return if $size == $newsize;
3045 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3047 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3049 $drive->{size
} = $newsize;
3050 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3052 PVE
::QemuConfig-
>write_config($vmid, $conf);
3055 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3059 __PACKAGE__-
>register_method({
3060 name
=> 'snapshot_list',
3061 path
=> '{vmid}/snapshot',
3063 description
=> "List all snapshots.",
3065 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3068 protected
=> 1, # qemu pid files are only readable by root
3070 additionalProperties
=> 0,
3072 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3073 node
=> get_standard_option
('pve-node'),
3082 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3087 my $vmid = $param->{vmid
};
3089 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3090 my $snaphash = $conf->{snapshots
} || {};
3094 foreach my $name (keys %$snaphash) {
3095 my $d = $snaphash->{$name};
3098 snaptime
=> $d->{snaptime
} || 0,
3099 vmstate
=> $d->{vmstate
} ?
1 : 0,
3100 description
=> $d->{description
} || '',
3102 $item->{parent
} = $d->{parent
} if $d->{parent
};
3103 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3107 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3108 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3109 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3111 push @$res, $current;
3116 __PACKAGE__-
>register_method({
3118 path
=> '{vmid}/snapshot',
3122 description
=> "Snapshot a VM.",
3124 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3127 additionalProperties
=> 0,
3129 node
=> get_standard_option
('pve-node'),
3130 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3131 snapname
=> get_standard_option
('pve-snapshot-name'),
3135 description
=> "Save the vmstate",
3140 description
=> "A textual description or comment.",
3146 description
=> "the task ID.",
3151 my $rpcenv = PVE
::RPCEnvironment
::get
();
3153 my $authuser = $rpcenv->get_user();
3155 my $node = extract_param
($param, 'node');
3157 my $vmid = extract_param
($param, 'vmid');
3159 my $snapname = extract_param
($param, 'snapname');
3161 die "unable to use snapshot name 'current' (reserved name)\n"
3162 if $snapname eq 'current';
3165 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3166 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3167 $param->{description
});
3170 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3173 __PACKAGE__-
>register_method({
3174 name
=> 'snapshot_cmd_idx',
3175 path
=> '{vmid}/snapshot/{snapname}',
3182 additionalProperties
=> 0,
3184 vmid
=> get_standard_option
('pve-vmid'),
3185 node
=> get_standard_option
('pve-node'),
3186 snapname
=> get_standard_option
('pve-snapshot-name'),
3195 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3202 push @$res, { cmd
=> 'rollback' };
3203 push @$res, { cmd
=> 'config' };
3208 __PACKAGE__-
>register_method({
3209 name
=> 'update_snapshot_config',
3210 path
=> '{vmid}/snapshot/{snapname}/config',
3214 description
=> "Update snapshot metadata.",
3216 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3219 additionalProperties
=> 0,
3221 node
=> get_standard_option
('pve-node'),
3222 vmid
=> get_standard_option
('pve-vmid'),
3223 snapname
=> get_standard_option
('pve-snapshot-name'),
3227 description
=> "A textual description or comment.",
3231 returns
=> { type
=> 'null' },
3235 my $rpcenv = PVE
::RPCEnvironment
::get
();
3237 my $authuser = $rpcenv->get_user();
3239 my $vmid = extract_param
($param, 'vmid');
3241 my $snapname = extract_param
($param, 'snapname');
3243 return undef if !defined($param->{description
});
3245 my $updatefn = sub {
3247 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3249 PVE
::QemuConfig-
>check_lock($conf);
3251 my $snap = $conf->{snapshots
}->{$snapname};
3253 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3255 $snap->{description
} = $param->{description
} if defined($param->{description
});
3257 PVE
::QemuConfig-
>write_config($vmid, $conf);
3260 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3265 __PACKAGE__-
>register_method({
3266 name
=> 'get_snapshot_config',
3267 path
=> '{vmid}/snapshot/{snapname}/config',
3270 description
=> "Get snapshot configuration",
3272 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3275 additionalProperties
=> 0,
3277 node
=> get_standard_option
('pve-node'),
3278 vmid
=> get_standard_option
('pve-vmid'),
3279 snapname
=> get_standard_option
('pve-snapshot-name'),
3282 returns
=> { type
=> "object" },
3286 my $rpcenv = PVE
::RPCEnvironment
::get
();
3288 my $authuser = $rpcenv->get_user();
3290 my $vmid = extract_param
($param, 'vmid');
3292 my $snapname = extract_param
($param, 'snapname');
3294 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3296 my $snap = $conf->{snapshots
}->{$snapname};
3298 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3303 __PACKAGE__-
>register_method({
3305 path
=> '{vmid}/snapshot/{snapname}/rollback',
3309 description
=> "Rollback VM state to specified snapshot.",
3311 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3314 additionalProperties
=> 0,
3316 node
=> get_standard_option
('pve-node'),
3317 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3318 snapname
=> get_standard_option
('pve-snapshot-name'),
3323 description
=> "the task ID.",
3328 my $rpcenv = PVE
::RPCEnvironment
::get
();
3330 my $authuser = $rpcenv->get_user();
3332 my $node = extract_param
($param, 'node');
3334 my $vmid = extract_param
($param, 'vmid');
3336 my $snapname = extract_param
($param, 'snapname');
3339 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3340 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3343 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3346 __PACKAGE__-
>register_method({
3347 name
=> 'delsnapshot',
3348 path
=> '{vmid}/snapshot/{snapname}',
3352 description
=> "Delete a VM snapshot.",
3354 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3357 additionalProperties
=> 0,
3359 node
=> get_standard_option
('pve-node'),
3360 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3361 snapname
=> get_standard_option
('pve-snapshot-name'),
3365 description
=> "For removal from config file, even if removing disk snapshots fails.",
3371 description
=> "the task ID.",
3376 my $rpcenv = PVE
::RPCEnvironment
::get
();
3378 my $authuser = $rpcenv->get_user();
3380 my $node = extract_param
($param, 'node');
3382 my $vmid = extract_param
($param, 'vmid');
3384 my $snapname = extract_param
($param, 'snapname');
3387 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3388 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3391 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3394 __PACKAGE__-
>register_method({
3396 path
=> '{vmid}/template',
3400 description
=> "Create a Template.",
3402 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3403 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3406 additionalProperties
=> 0,
3408 node
=> get_standard_option
('pve-node'),
3409 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3413 description
=> "If you want to convert only 1 disk to base image.",
3414 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3419 returns
=> { type
=> 'null'},
3423 my $rpcenv = PVE
::RPCEnvironment
::get
();
3425 my $authuser = $rpcenv->get_user();
3427 my $node = extract_param
($param, 'node');
3429 my $vmid = extract_param
($param, 'vmid');
3431 my $disk = extract_param
($param, 'disk');
3433 my $updatefn = sub {
3435 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3437 PVE
::QemuConfig-
>check_lock($conf);
3439 die "unable to create template, because VM contains snapshots\n"
3440 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3442 die "you can't convert a template to a template\n"
3443 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3445 die "you can't convert a VM to template if VM is running\n"
3446 if PVE
::QemuServer
::check_running
($vmid);
3449 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3452 $conf->{template
} = 1;
3453 PVE
::QemuConfig-
>write_config($vmid, $conf);
3455 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3458 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);