1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::RPCEnvironment
;
20 use PVE
::AccessControl
;
24 use PVE
::API2
::Firewall
::VM
;
25 use PVE
::HA
::Env
::PVE2
;
28 use Data
::Dumper
; # fixme: remove
30 use base
qw(PVE::RESTHandler);
32 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
34 my $resolve_cdrom_alias = sub {
37 if (my $value = $param->{cdrom
}) {
38 $value .= ",media=cdrom" if $value !~ m/media=/;
39 $param->{ide2
} = $value;
40 delete $param->{cdrom
};
44 my $check_storage_access = sub {
45 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
47 PVE
::QemuServer
::foreach_drive
($settings, sub {
48 my ($ds, $drive) = @_;
50 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
52 my $volid = $drive->{file
};
54 if (!$volid || $volid eq 'none') {
56 } elsif ($isCDROM && ($volid eq 'cdrom')) {
57 $rpcenv->check($authuser, "/", ['Sys.Console']);
58 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
59 my ($storeid, $size) = ($2 || $default_storage, $3);
60 die "no storage ID specified (and no default storage)\n" if !$storeid;
61 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
63 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
68 my $check_storage_access_clone = sub {
69 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
73 PVE
::QemuServer
::foreach_drive
($conf, sub {
74 my ($ds, $drive) = @_;
76 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
78 my $volid = $drive->{file
};
80 return if !$volid || $volid eq 'none';
83 if ($volid eq 'cdrom') {
84 $rpcenv->check($authuser, "/", ['Sys.Console']);
86 # we simply allow access
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
93 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
94 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
95 $sharedvm = 0 if !$scfg->{shared
};
97 $sid = $storage if $storage;
98 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
105 # Note: $pool is only needed when creating a VM, because pool permissions
106 # are automatically inherited if VM already exists inside a pool.
107 my $create_disks = sub {
108 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
113 PVE
::QemuServer
::foreach_drive
($settings, sub {
114 my ($ds, $disk) = @_;
116 my $volid = $disk->{file
};
118 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
119 delete $disk->{size
};
120 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
121 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
122 my ($storeid, $size) = ($2 || $default_storage, $3);
123 die "no storage ID specified (and no default storage)\n" if !$storeid;
124 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
125 my $fmt = $disk->{format
} || $defformat;
128 if ($ds eq 'efidisk0') {
130 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
131 die "uefi vars image not found\n" if ! -f
$ovmfvars;
132 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
134 $disk->{file
} = $volid;
135 $disk->{size
} = 128*1024;
136 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
137 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
138 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
139 my $path = PVE
::Storage
::path
($storecfg, $volid);
140 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-f', 'raw', '-O', $qemufmt];
141 push @$efidiskcmd, $ovmfvars;
142 push @$efidiskcmd, $path;
143 eval { PVE
::Tools
::run_command
($efidiskcmd); };
145 die "Copying of EFI Vars image failed: $err" if $err;
147 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
148 $fmt, undef, $size*1024*1024);
149 $disk->{file
} = $volid;
150 $disk->{size
} = $size*1024*1024*1024;
152 push @$vollist, $volid;
153 delete $disk->{format
}; # no longer needed
154 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
157 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
159 my $volid_is_new = 1;
162 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
163 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
168 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
170 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
172 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
174 die "volume $volid does not exists\n" if !$size;
176 $disk->{size
} = $size;
179 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
183 # free allocated images on error
185 syslog
('err', "VM $vmid creating disks failed");
186 foreach my $volid (@$vollist) {
187 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
193 # modify vm config if everything went well
194 foreach my $ds (keys %$res) {
195 $conf->{$ds} = $res->{$ds};
212 my $memoryoptions = {
218 my $hwtypeoptions = {
230 my $generaloptions = {
237 'migrate_downtime' => 1,
238 'migrate_speed' => 1,
250 my $vmpoweroptions = {
259 my $check_vm_modify_config_perm = sub {
260 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
262 return 1 if $authuser eq 'root@pam';
264 foreach my $opt (@$key_list) {
265 # disk checks need to be done somewhere else
266 next if PVE
::QemuServer
::is_valid_drivename
($opt);
267 next if $opt eq 'cdrom';
268 next if $opt =~ m/^unused\d+$/;
270 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
271 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
272 } elsif ($memoryoptions->{$opt}) {
273 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
274 } elsif ($hwtypeoptions->{$opt}) {
275 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
276 } elsif ($generaloptions->{$opt}) {
277 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
278 # special case for startup since it changes host behaviour
279 if ($opt eq 'startup') {
280 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
282 } elsif ($vmpoweroptions->{$opt}) {
283 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
284 } elsif ($diskoptions->{$opt}) {
285 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
286 } elsif ($opt =~ m/^net\d+$/) {
287 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
289 # catches usb\d+, hostpci\d+, args, lock, etc.
290 # new options will be checked here
291 die "only root can set '$opt' config\n";
298 __PACKAGE__-
>register_method({
302 description
=> "Virtual machine index (per node).",
304 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
308 protected
=> 1, # qemu pid files are only readable by root
310 additionalProperties
=> 0,
312 node
=> get_standard_option
('pve-node'),
316 description
=> "Determine the full status of active VMs.",
326 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
331 my $rpcenv = PVE
::RPCEnvironment
::get
();
332 my $authuser = $rpcenv->get_user();
334 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
337 foreach my $vmid (keys %$vmstatus) {
338 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
340 my $data = $vmstatus->{$vmid};
341 $data->{vmid
} = int($vmid);
350 __PACKAGE__-
>register_method({
354 description
=> "Create or restore a virtual machine.",
356 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
357 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
358 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
359 user
=> 'all', # check inside
364 additionalProperties
=> 0,
365 properties
=> PVE
::QemuServer
::json_config_properties
(
367 node
=> get_standard_option
('pve-node'),
368 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
370 description
=> "The backup file.",
374 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
376 storage
=> get_standard_option
('pve-storage-id', {
377 description
=> "Default storage.",
379 completion
=> \
&PVE
::QemuServer
::complete_storage
,
384 description
=> "Allow to overwrite existing VM.",
385 requires
=> 'archive',
390 description
=> "Assign a unique random ethernet address.",
391 requires
=> 'archive',
395 type
=> 'string', format
=> 'pve-poolid',
396 description
=> "Add the VM to the specified pool.",
406 my $rpcenv = PVE
::RPCEnvironment
::get
();
408 my $authuser = $rpcenv->get_user();
410 my $node = extract_param
($param, 'node');
412 my $vmid = extract_param
($param, 'vmid');
414 my $archive = extract_param
($param, 'archive');
416 my $storage = extract_param
($param, 'storage');
418 my $force = extract_param
($param, 'force');
420 my $unique = extract_param
($param, 'unique');
422 my $pool = extract_param
($param, 'pool');
424 my $filename = PVE
::QemuConfig-
>config_file($vmid);
426 my $storecfg = PVE
::Storage
::config
();
428 PVE
::Cluster
::check_cfs_quorum
();
430 if (defined($pool)) {
431 $rpcenv->check_pool_exist($pool);
434 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
435 if defined($storage);
437 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
439 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
441 } elsif ($archive && $force && (-f
$filename) &&
442 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
443 # OK: user has VM.Backup permissions, and want to restore an existing VM
449 &$resolve_cdrom_alias($param);
451 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
453 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
455 foreach my $opt (keys %$param) {
456 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
457 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
458 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
460 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
461 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
465 PVE
::QemuServer
::add_random_macs
($param);
467 my $keystr = join(' ', keys %$param);
468 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
470 if ($archive eq '-') {
471 die "pipe requires cli environment\n"
472 if $rpcenv->{type
} ne 'cli';
474 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
475 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
479 my $restorefn = sub {
480 my $vmlist = PVE
::Cluster
::get_vmlist
();
481 if ($vmlist->{ids
}->{$vmid}) {
482 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
483 if ($current_node eq $node) {
484 my $conf = PVE
::QemuConfig-
>load_config($vmid);
486 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
488 die "unable to restore vm $vmid - config file already exists\n"
491 die "unable to restore vm $vmid - vm is running\n"
492 if PVE
::QemuServer
::check_running
($vmid);
494 die "unable to restore vm $vmid - vm is a template\n"
495 if PVE
::QemuConfig-
>is_template($conf);
498 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
503 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
506 unique
=> $unique });
508 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
511 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
517 PVE
::Cluster
::check_vmid_unused
($vmid);
527 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
529 # try to be smart about bootdisk
530 my @disks = PVE
::QemuServer
::valid_drive_names
();
532 foreach my $ds (reverse @disks) {
533 next if !$conf->{$ds};
534 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
535 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
539 if (!$conf->{bootdisk
} && $firstdisk) {
540 $conf->{bootdisk
} = $firstdisk;
543 # auto generate uuid if user did not specify smbios1 option
544 if (!$conf->{smbios1
}) {
545 my ($uuid, $uuid_str);
546 UUID
::generate
($uuid);
547 UUID
::unparse
($uuid, $uuid_str);
548 $conf->{smbios1
} = "uuid=$uuid_str";
551 PVE
::QemuConfig-
>write_config($vmid, $conf);
557 foreach my $volid (@$vollist) {
558 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
561 die "create failed - $err";
564 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
567 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
570 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
573 __PACKAGE__-
>register_method({
578 description
=> "Directory index",
583 additionalProperties
=> 0,
585 node
=> get_standard_option
('pve-node'),
586 vmid
=> get_standard_option
('pve-vmid'),
594 subdir
=> { type
=> 'string' },
597 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
603 { subdir
=> 'config' },
604 { subdir
=> 'pending' },
605 { subdir
=> 'status' },
606 { subdir
=> 'unlink' },
607 { subdir
=> 'vncproxy' },
608 { subdir
=> 'migrate' },
609 { subdir
=> 'resize' },
610 { subdir
=> 'move' },
612 { subdir
=> 'rrddata' },
613 { subdir
=> 'monitor' },
614 { subdir
=> 'snapshot' },
615 { subdir
=> 'spiceproxy' },
616 { subdir
=> 'sendkey' },
617 { subdir
=> 'firewall' },
623 __PACKAGE__-
>register_method ({
624 subclass
=> "PVE::API2::Firewall::VM",
625 path
=> '{vmid}/firewall',
628 __PACKAGE__-
>register_method({
630 path
=> '{vmid}/rrd',
632 protected
=> 1, # fixme: can we avoid that?
634 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
636 description
=> "Read VM RRD statistics (returns PNG)",
638 additionalProperties
=> 0,
640 node
=> get_standard_option
('pve-node'),
641 vmid
=> get_standard_option
('pve-vmid'),
643 description
=> "Specify the time frame you are interested in.",
645 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
648 description
=> "The list of datasources you want to display.",
649 type
=> 'string', format
=> 'pve-configid-list',
652 description
=> "The RRD consolidation function",
654 enum
=> [ 'AVERAGE', 'MAX' ],
662 filename
=> { type
=> 'string' },
668 return PVE
::Cluster
::create_rrd_graph
(
669 "pve2-vm/$param->{vmid}", $param->{timeframe
},
670 $param->{ds
}, $param->{cf
});
674 __PACKAGE__-
>register_method({
676 path
=> '{vmid}/rrddata',
678 protected
=> 1, # fixme: can we avoid that?
680 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
682 description
=> "Read VM RRD statistics",
684 additionalProperties
=> 0,
686 node
=> get_standard_option
('pve-node'),
687 vmid
=> get_standard_option
('pve-vmid'),
689 description
=> "Specify the time frame you are interested in.",
691 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
694 description
=> "The RRD consolidation function",
696 enum
=> [ 'AVERAGE', 'MAX' ],
711 return PVE
::Cluster
::create_rrd_data
(
712 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
716 __PACKAGE__-
>register_method({
718 path
=> '{vmid}/config',
721 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
723 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
726 additionalProperties
=> 0,
728 node
=> get_standard_option
('pve-node'),
729 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
731 description
=> "Get current values (instead of pending values).",
743 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
750 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
752 delete $conf->{snapshots
};
754 if (!$param->{current
}) {
755 foreach my $opt (keys %{$conf->{pending
}}) {
756 next if $opt eq 'delete';
757 my $value = $conf->{pending
}->{$opt};
758 next if ref($value); # just to be sure
759 $conf->{$opt} = $value;
761 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
762 foreach my $opt (keys %$pending_delete_hash) {
763 delete $conf->{$opt} if $conf->{$opt};
767 delete $conf->{pending
};
772 __PACKAGE__-
>register_method({
773 name
=> 'vm_pending',
774 path
=> '{vmid}/pending',
777 description
=> "Get virtual machine configuration, including pending changes.",
779 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
782 additionalProperties
=> 0,
784 node
=> get_standard_option
('pve-node'),
785 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
794 description
=> "Configuration option name.",
798 description
=> "Current value.",
803 description
=> "Pending value.",
808 description
=> "Indicates a pending delete request if present and not 0. " .
809 "The value 2 indicates a force-delete request.",
821 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
823 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
827 foreach my $opt (keys %$conf) {
828 next if ref($conf->{$opt});
829 my $item = { key
=> $opt };
830 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
831 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
832 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
836 foreach my $opt (keys %{$conf->{pending
}}) {
837 next if $opt eq 'delete';
838 next if ref($conf->{pending
}->{$opt}); # just to be sure
839 next if defined($conf->{$opt});
840 my $item = { key
=> $opt };
841 $item->{pending
} = $conf->{pending
}->{$opt};
845 while (my ($opt, $force) = each %$pending_delete_hash) {
846 next if $conf->{pending
}->{$opt}; # just to be sure
847 next if $conf->{$opt};
848 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
855 # POST/PUT {vmid}/config implementation
857 # The original API used PUT (idempotent) an we assumed that all operations
858 # are fast. But it turned out that almost any configuration change can
859 # involve hot-plug actions, or disk alloc/free. Such actions can take long
860 # time to complete and have side effects (not idempotent).
862 # The new implementation uses POST and forks a worker process. We added
863 # a new option 'background_delay'. If specified we wait up to
864 # 'background_delay' second for the worker task to complete. It returns null
865 # if the task is finished within that time, else we return the UPID.
867 my $update_vm_api = sub {
868 my ($param, $sync) = @_;
870 my $rpcenv = PVE
::RPCEnvironment
::get
();
872 my $authuser = $rpcenv->get_user();
874 my $node = extract_param
($param, 'node');
876 my $vmid = extract_param
($param, 'vmid');
878 my $digest = extract_param
($param, 'digest');
880 my $background_delay = extract_param
($param, 'background_delay');
882 my @paramarr = (); # used for log message
883 foreach my $key (keys %$param) {
884 push @paramarr, "-$key", $param->{$key};
887 my $skiplock = extract_param
($param, 'skiplock');
888 raise_param_exc
({ skiplock
=> "Only root may use this option." })
889 if $skiplock && $authuser ne 'root@pam';
891 my $delete_str = extract_param
($param, 'delete');
893 my $revert_str = extract_param
($param, 'revert');
895 my $force = extract_param
($param, 'force');
897 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
899 my $storecfg = PVE
::Storage
::config
();
901 my $defaults = PVE
::QemuServer
::load_defaults
();
903 &$resolve_cdrom_alias($param);
905 # now try to verify all parameters
908 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
909 if (!PVE
::QemuServer
::option_exists
($opt)) {
910 raise_param_exc
({ revert
=> "unknown option '$opt'" });
913 raise_param_exc
({ delete => "you can't use '-$opt' and " .
914 "-revert $opt' at the same time" })
915 if defined($param->{$opt});
921 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
922 $opt = 'ide2' if $opt eq 'cdrom';
924 raise_param_exc
({ delete => "you can't use '-$opt' and " .
925 "-delete $opt' at the same time" })
926 if defined($param->{$opt});
928 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
929 "-revert $opt' at the same time" })
932 if (!PVE
::QemuServer
::option_exists
($opt)) {
933 raise_param_exc
({ delete => "unknown option '$opt'" });
939 foreach my $opt (keys %$param) {
940 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
942 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
943 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
944 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
945 } elsif ($opt =~ m/^net(\d+)$/) {
947 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
948 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
952 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
954 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
956 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
960 my $conf = PVE
::QemuConfig-
>load_config($vmid);
962 die "checksum missmatch (file change by other user?)\n"
963 if $digest && $digest ne $conf->{digest
};
965 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
967 foreach my $opt (keys %$revert) {
968 if (defined($conf->{$opt})) {
969 $param->{$opt} = $conf->{$opt};
970 } elsif (defined($conf->{pending
}->{$opt})) {
975 if ($param->{memory
} || defined($param->{balloon
})) {
976 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
977 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
979 die "balloon value too large (must be smaller than assigned memory)\n"
980 if $balloon && $balloon > $maxmem;
983 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
987 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
989 # write updates to pending section
991 my $modified = {}; # record what $option we modify
993 foreach my $opt (@delete) {
994 $modified->{$opt} = 1;
995 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
996 if ($opt =~ m/^unused/) {
997 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
998 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
999 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1000 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1001 delete $conf->{$opt};
1002 PVE
::QemuConfig-
>write_config($vmid, $conf);
1004 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1005 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1006 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1007 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1008 if defined($conf->{pending
}->{$opt});
1009 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1010 PVE
::QemuConfig-
>write_config($vmid, $conf);
1012 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1013 PVE
::QemuConfig-
>write_config($vmid, $conf);
1017 foreach my $opt (keys %$param) { # add/change
1018 $modified->{$opt} = 1;
1019 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1020 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1022 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1023 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1024 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1025 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1027 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1029 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1030 if defined($conf->{pending
}->{$opt});
1032 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1034 $conf->{pending
}->{$opt} = $param->{$opt};
1036 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1037 PVE
::QemuConfig-
>write_config($vmid, $conf);
1040 # remove pending changes when nothing changed
1041 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1042 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1043 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1045 return if !scalar(keys %{$conf->{pending
}});
1047 my $running = PVE
::QemuServer
::check_running
($vmid);
1049 # apply pending changes
1051 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1055 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1056 raise_param_exc
($errors) if scalar(keys %$errors);
1058 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1068 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1070 if ($background_delay) {
1072 # Note: It would be better to do that in the Event based HTTPServer
1073 # to avoid blocking call to sleep.
1075 my $end_time = time() + $background_delay;
1077 my $task = PVE
::Tools
::upid_decode
($upid);
1080 while (time() < $end_time) {
1081 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1083 sleep(1); # this gets interrupted when child process ends
1087 my $status = PVE
::Tools
::upid_read_status
($upid);
1088 return undef if $status eq 'OK';
1097 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1100 my $vm_config_perm_list = [
1105 'VM.Config.Network',
1107 'VM.Config.Options',
1110 __PACKAGE__-
>register_method({
1111 name
=> 'update_vm_async',
1112 path
=> '{vmid}/config',
1116 description
=> "Set virtual machine options (asynchrounous API).",
1118 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1121 additionalProperties
=> 0,
1122 properties
=> PVE
::QemuServer
::json_config_properties
(
1124 node
=> get_standard_option
('pve-node'),
1125 vmid
=> get_standard_option
('pve-vmid'),
1126 skiplock
=> get_standard_option
('skiplock'),
1128 type
=> 'string', format
=> 'pve-configid-list',
1129 description
=> "A list of settings you want to delete.",
1133 type
=> 'string', format
=> 'pve-configid-list',
1134 description
=> "Revert a pending change.",
1139 description
=> $opt_force_description,
1141 requires
=> 'delete',
1145 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1149 background_delay
=> {
1151 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1162 code
=> $update_vm_api,
1165 __PACKAGE__-
>register_method({
1166 name
=> 'update_vm',
1167 path
=> '{vmid}/config',
1171 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1173 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1176 additionalProperties
=> 0,
1177 properties
=> PVE
::QemuServer
::json_config_properties
(
1179 node
=> get_standard_option
('pve-node'),
1180 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1181 skiplock
=> get_standard_option
('skiplock'),
1183 type
=> 'string', format
=> 'pve-configid-list',
1184 description
=> "A list of settings you want to delete.",
1188 type
=> 'string', format
=> 'pve-configid-list',
1189 description
=> "Revert a pending change.",
1194 description
=> $opt_force_description,
1196 requires
=> 'delete',
1200 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1206 returns
=> { type
=> 'null' },
1209 &$update_vm_api($param, 1);
1215 __PACKAGE__-
>register_method({
1216 name
=> 'destroy_vm',
1221 description
=> "Destroy the vm (also delete all used/owned volumes).",
1223 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1226 additionalProperties
=> 0,
1228 node
=> get_standard_option
('pve-node'),
1229 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1230 skiplock
=> get_standard_option
('skiplock'),
1239 my $rpcenv = PVE
::RPCEnvironment
::get
();
1241 my $authuser = $rpcenv->get_user();
1243 my $vmid = $param->{vmid
};
1245 my $skiplock = $param->{skiplock
};
1246 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1247 if $skiplock && $authuser ne 'root@pam';
1250 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1252 my $storecfg = PVE
::Storage
::config
();
1254 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1256 die "unable to remove VM $vmid - used in HA resources\n"
1257 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1259 # early tests (repeat after locking)
1260 die "VM $vmid is running - destroy failed\n"
1261 if PVE
::QemuServer
::check_running
($vmid);
1266 syslog
('info', "destroy VM $vmid: $upid\n");
1268 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1270 PVE
::AccessControl
::remove_vm_access
($vmid);
1272 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1275 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1278 __PACKAGE__-
>register_method({
1280 path
=> '{vmid}/unlink',
1284 description
=> "Unlink/delete disk images.",
1286 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1289 additionalProperties
=> 0,
1291 node
=> get_standard_option
('pve-node'),
1292 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1294 type
=> 'string', format
=> 'pve-configid-list',
1295 description
=> "A list of disk IDs you want to delete.",
1299 description
=> $opt_force_description,
1304 returns
=> { type
=> 'null'},
1308 $param->{delete} = extract_param
($param, 'idlist');
1310 __PACKAGE__-
>update_vm($param);
1317 __PACKAGE__-
>register_method({
1319 path
=> '{vmid}/vncproxy',
1323 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1325 description
=> "Creates a TCP VNC proxy connections.",
1327 additionalProperties
=> 0,
1329 node
=> get_standard_option
('pve-node'),
1330 vmid
=> get_standard_option
('pve-vmid'),
1334 description
=> "starts websockify instead of vncproxy",
1339 additionalProperties
=> 0,
1341 user
=> { type
=> 'string' },
1342 ticket
=> { type
=> 'string' },
1343 cert
=> { type
=> 'string' },
1344 port
=> { type
=> 'integer' },
1345 upid
=> { type
=> 'string' },
1351 my $rpcenv = PVE
::RPCEnvironment
::get
();
1353 my $authuser = $rpcenv->get_user();
1355 my $vmid = $param->{vmid
};
1356 my $node = $param->{node
};
1357 my $websocket = $param->{websocket
};
1359 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1361 my $authpath = "/vms/$vmid";
1363 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1365 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1368 my ($remip, $family);
1371 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1372 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1373 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1374 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1376 $family = PVE
::Tools
::get_host_address_family
($node);
1379 my $port = PVE
::Tools
::next_vnc_port
($family);
1386 syslog
('info', "starting vnc proxy $upid\n");
1390 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1392 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1394 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1395 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1396 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1397 '-timeout', $timeout, '-authpath', $authpath,
1398 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1401 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1403 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1405 my $qmstr = join(' ', @$qmcmd);
1407 # also redirect stderr (else we get RFB protocol errors)
1408 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1411 PVE
::Tools
::run_command
($cmd);
1416 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1418 PVE
::Tools
::wait_for_vnc_port
($port);
1429 __PACKAGE__-
>register_method({
1430 name
=> 'vncwebsocket',
1431 path
=> '{vmid}/vncwebsocket',
1434 description
=> "You also need to pass a valid ticket (vncticket).",
1435 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1437 description
=> "Opens a weksocket for VNC traffic.",
1439 additionalProperties
=> 0,
1441 node
=> get_standard_option
('pve-node'),
1442 vmid
=> get_standard_option
('pve-vmid'),
1444 description
=> "Ticket from previous call to vncproxy.",
1449 description
=> "Port number returned by previous vncproxy call.",
1459 port
=> { type
=> 'string' },
1465 my $rpcenv = PVE
::RPCEnvironment
::get
();
1467 my $authuser = $rpcenv->get_user();
1469 my $vmid = $param->{vmid
};
1470 my $node = $param->{node
};
1472 my $authpath = "/vms/$vmid";
1474 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1476 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1478 # Note: VNC ports are acessible from outside, so we do not gain any
1479 # security if we verify that $param->{port} belongs to VM $vmid. This
1480 # check is done by verifying the VNC ticket (inside VNC protocol).
1482 my $port = $param->{port
};
1484 return { port
=> $port };
1487 __PACKAGE__-
>register_method({
1488 name
=> 'spiceproxy',
1489 path
=> '{vmid}/spiceproxy',
1494 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1496 description
=> "Returns a SPICE configuration to connect to the VM.",
1498 additionalProperties
=> 0,
1500 node
=> get_standard_option
('pve-node'),
1501 vmid
=> get_standard_option
('pve-vmid'),
1502 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1505 returns
=> get_standard_option
('remote-viewer-config'),
1509 my $rpcenv = PVE
::RPCEnvironment
::get
();
1511 my $authuser = $rpcenv->get_user();
1513 my $vmid = $param->{vmid
};
1514 my $node = $param->{node
};
1515 my $proxy = $param->{proxy
};
1517 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1518 my $title = "VM $vmid";
1519 $title .= " - ". $conf->{name
} if $conf->{name
};
1521 my $port = PVE
::QemuServer
::spice_port
($vmid);
1523 my ($ticket, undef, $remote_viewer_config) =
1524 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1526 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1527 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1529 return $remote_viewer_config;
1532 __PACKAGE__-
>register_method({
1534 path
=> '{vmid}/status',
1537 description
=> "Directory index",
1542 additionalProperties
=> 0,
1544 node
=> get_standard_option
('pve-node'),
1545 vmid
=> get_standard_option
('pve-vmid'),
1553 subdir
=> { type
=> 'string' },
1556 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1562 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1565 { subdir
=> 'current' },
1566 { subdir
=> 'start' },
1567 { subdir
=> 'stop' },
1573 __PACKAGE__-
>register_method({
1574 name
=> 'vm_status',
1575 path
=> '{vmid}/status/current',
1578 protected
=> 1, # qemu pid files are only readable by root
1579 description
=> "Get virtual machine status.",
1581 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1584 additionalProperties
=> 0,
1586 node
=> get_standard_option
('pve-node'),
1587 vmid
=> get_standard_option
('pve-vmid'),
1590 returns
=> { type
=> 'object' },
1595 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1597 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1598 my $status = $vmstatus->{$param->{vmid
}};
1600 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1602 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1607 __PACKAGE__-
>register_method({
1609 path
=> '{vmid}/status/start',
1613 description
=> "Start virtual machine.",
1615 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1618 additionalProperties
=> 0,
1620 node
=> get_standard_option
('pve-node'),
1621 vmid
=> get_standard_option
('pve-vmid',
1622 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1623 skiplock
=> get_standard_option
('skiplock'),
1624 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1625 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1626 machine
=> get_standard_option
('pve-qm-machine'),
1635 my $rpcenv = PVE
::RPCEnvironment
::get
();
1637 my $authuser = $rpcenv->get_user();
1639 my $node = extract_param
($param, 'node');
1641 my $vmid = extract_param
($param, 'vmid');
1643 my $machine = extract_param
($param, 'machine');
1645 my $stateuri = extract_param
($param, 'stateuri');
1646 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1647 if $stateuri && $authuser ne 'root@pam';
1649 my $skiplock = extract_param
($param, 'skiplock');
1650 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1651 if $skiplock && $authuser ne 'root@pam';
1653 my $migratedfrom = extract_param
($param, 'migratedfrom');
1654 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1655 if $migratedfrom && $authuser ne 'root@pam';
1657 # read spice ticket from STDIN
1659 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1660 if (defined(my $line = <>)) {
1662 $spice_ticket = $line;
1666 PVE
::Cluster
::check_cfs_quorum
();
1668 my $storecfg = PVE
::Storage
::config
();
1670 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1671 $rpcenv->{type
} ne 'ha') {
1676 my $service = "vm:$vmid";
1678 my $cmd = ['ha-manager', 'enable', $service];
1680 print "Executing HA start for VM $vmid\n";
1682 PVE
::Tools
::run_command
($cmd);
1687 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1694 syslog
('info', "start VM $vmid: $upid\n");
1696 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1697 $machine, $spice_ticket);
1702 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1706 __PACKAGE__-
>register_method({
1708 path
=> '{vmid}/status/stop',
1712 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1713 "is akin to pulling the power plug of a running computer and may damage the VM data",
1715 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1718 additionalProperties
=> 0,
1720 node
=> get_standard_option
('pve-node'),
1721 vmid
=> get_standard_option
('pve-vmid',
1722 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1723 skiplock
=> get_standard_option
('skiplock'),
1724 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1726 description
=> "Wait maximal timeout seconds.",
1732 description
=> "Do not decativate storage volumes.",
1745 my $rpcenv = PVE
::RPCEnvironment
::get
();
1747 my $authuser = $rpcenv->get_user();
1749 my $node = extract_param
($param, 'node');
1751 my $vmid = extract_param
($param, 'vmid');
1753 my $skiplock = extract_param
($param, 'skiplock');
1754 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1755 if $skiplock && $authuser ne 'root@pam';
1757 my $keepActive = extract_param
($param, 'keepActive');
1758 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1759 if $keepActive && $authuser ne 'root@pam';
1761 my $migratedfrom = extract_param
($param, 'migratedfrom');
1762 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1763 if $migratedfrom && $authuser ne 'root@pam';
1766 my $storecfg = PVE
::Storage
::config
();
1768 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1773 my $service = "vm:$vmid";
1775 my $cmd = ['ha-manager', 'disable', $service];
1777 print "Executing HA stop for VM $vmid\n";
1779 PVE
::Tools
::run_command
($cmd);
1784 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1790 syslog
('info', "stop VM $vmid: $upid\n");
1792 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1793 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1798 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1802 __PACKAGE__-
>register_method({
1804 path
=> '{vmid}/status/reset',
1808 description
=> "Reset virtual machine.",
1810 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1813 additionalProperties
=> 0,
1815 node
=> get_standard_option
('pve-node'),
1816 vmid
=> get_standard_option
('pve-vmid',
1817 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1818 skiplock
=> get_standard_option
('skiplock'),
1827 my $rpcenv = PVE
::RPCEnvironment
::get
();
1829 my $authuser = $rpcenv->get_user();
1831 my $node = extract_param
($param, 'node');
1833 my $vmid = extract_param
($param, 'vmid');
1835 my $skiplock = extract_param
($param, 'skiplock');
1836 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1837 if $skiplock && $authuser ne 'root@pam';
1839 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1844 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1849 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1852 __PACKAGE__-
>register_method({
1853 name
=> 'vm_shutdown',
1854 path
=> '{vmid}/status/shutdown',
1858 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1859 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1861 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1864 additionalProperties
=> 0,
1866 node
=> get_standard_option
('pve-node'),
1867 vmid
=> get_standard_option
('pve-vmid',
1868 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1869 skiplock
=> get_standard_option
('skiplock'),
1871 description
=> "Wait maximal timeout seconds.",
1877 description
=> "Make sure the VM stops.",
1883 description
=> "Do not decativate storage volumes.",
1896 my $rpcenv = PVE
::RPCEnvironment
::get
();
1898 my $authuser = $rpcenv->get_user();
1900 my $node = extract_param
($param, 'node');
1902 my $vmid = extract_param
($param, 'vmid');
1904 my $skiplock = extract_param
($param, 'skiplock');
1905 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1906 if $skiplock && $authuser ne 'root@pam';
1908 my $keepActive = extract_param
($param, 'keepActive');
1909 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1910 if $keepActive && $authuser ne 'root@pam';
1912 my $storecfg = PVE
::Storage
::config
();
1916 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1917 # otherwise, we will infer a shutdown command, but run into the timeout,
1918 # then when the vm is resumed, it will instantly shutdown
1920 # checking the qmp status here to get feedback to the gui/cli/api
1921 # and the status query should not take too long
1924 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1928 if (!$err && $qmpstatus->{status
} eq "paused") {
1929 if ($param->{forceStop
}) {
1930 warn "VM is paused - stop instead of shutdown\n";
1933 die "VM is paused - cannot shutdown\n";
1940 syslog
('info', "shutdown VM $vmid: $upid\n");
1942 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1943 $shutdown, $param->{forceStop
}, $keepActive);
1948 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1951 __PACKAGE__-
>register_method({
1952 name
=> 'vm_suspend',
1953 path
=> '{vmid}/status/suspend',
1957 description
=> "Suspend virtual machine.",
1959 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1962 additionalProperties
=> 0,
1964 node
=> get_standard_option
('pve-node'),
1965 vmid
=> get_standard_option
('pve-vmid',
1966 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1967 skiplock
=> get_standard_option
('skiplock'),
1976 my $rpcenv = PVE
::RPCEnvironment
::get
();
1978 my $authuser = $rpcenv->get_user();
1980 my $node = extract_param
($param, 'node');
1982 my $vmid = extract_param
($param, 'vmid');
1984 my $skiplock = extract_param
($param, 'skiplock');
1985 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1986 if $skiplock && $authuser ne 'root@pam';
1988 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1993 syslog
('info', "suspend VM $vmid: $upid\n");
1995 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2000 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2003 __PACKAGE__-
>register_method({
2004 name
=> 'vm_resume',
2005 path
=> '{vmid}/status/resume',
2009 description
=> "Resume virtual machine.",
2011 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2014 additionalProperties
=> 0,
2016 node
=> get_standard_option
('pve-node'),
2017 vmid
=> get_standard_option
('pve-vmid',
2018 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2019 skiplock
=> get_standard_option
('skiplock'),
2020 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2030 my $rpcenv = PVE
::RPCEnvironment
::get
();
2032 my $authuser = $rpcenv->get_user();
2034 my $node = extract_param
($param, 'node');
2036 my $vmid = extract_param
($param, 'vmid');
2038 my $skiplock = extract_param
($param, 'skiplock');
2039 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2040 if $skiplock && $authuser ne 'root@pam';
2042 my $nocheck = extract_param
($param, 'nocheck');
2044 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2049 syslog
('info', "resume VM $vmid: $upid\n");
2051 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2056 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2059 __PACKAGE__-
>register_method({
2060 name
=> 'vm_sendkey',
2061 path
=> '{vmid}/sendkey',
2065 description
=> "Send key event to virtual machine.",
2067 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2070 additionalProperties
=> 0,
2072 node
=> get_standard_option
('pve-node'),
2073 vmid
=> get_standard_option
('pve-vmid',
2074 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2075 skiplock
=> get_standard_option
('skiplock'),
2077 description
=> "The key (qemu monitor encoding).",
2082 returns
=> { type
=> 'null'},
2086 my $rpcenv = PVE
::RPCEnvironment
::get
();
2088 my $authuser = $rpcenv->get_user();
2090 my $node = extract_param
($param, 'node');
2092 my $vmid = extract_param
($param, 'vmid');
2094 my $skiplock = extract_param
($param, 'skiplock');
2095 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2096 if $skiplock && $authuser ne 'root@pam';
2098 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2103 __PACKAGE__-
>register_method({
2104 name
=> 'vm_feature',
2105 path
=> '{vmid}/feature',
2109 description
=> "Check if feature for virtual machine is available.",
2111 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2114 additionalProperties
=> 0,
2116 node
=> get_standard_option
('pve-node'),
2117 vmid
=> get_standard_option
('pve-vmid'),
2119 description
=> "Feature to check.",
2121 enum
=> [ 'snapshot', 'clone', 'copy' ],
2123 snapname
=> get_standard_option
('pve-snapshot-name', {
2131 hasFeature
=> { type
=> 'boolean' },
2134 items
=> { type
=> 'string' },
2141 my $node = extract_param
($param, 'node');
2143 my $vmid = extract_param
($param, 'vmid');
2145 my $snapname = extract_param
($param, 'snapname');
2147 my $feature = extract_param
($param, 'feature');
2149 my $running = PVE
::QemuServer
::check_running
($vmid);
2151 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2154 my $snap = $conf->{snapshots
}->{$snapname};
2155 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2158 my $storecfg = PVE
::Storage
::config
();
2160 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2161 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2164 hasFeature
=> $hasFeature,
2165 nodes
=> [ keys %$nodelist ],
2169 __PACKAGE__-
>register_method({
2171 path
=> '{vmid}/clone',
2175 description
=> "Create a copy of virtual machine/template.",
2177 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2178 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2179 "'Datastore.AllocateSpace' on any used storage.",
2182 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2184 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2185 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2190 additionalProperties
=> 0,
2192 node
=> get_standard_option
('pve-node'),
2193 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2194 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2197 type
=> 'string', format
=> 'dns-name',
2198 description
=> "Set a name for the new VM.",
2203 description
=> "Description for the new VM.",
2207 type
=> 'string', format
=> 'pve-poolid',
2208 description
=> "Add the new VM to the specified pool.",
2210 snapname
=> get_standard_option
('pve-snapshot-name', {
2213 storage
=> get_standard_option
('pve-storage-id', {
2214 description
=> "Target storage for full clone.",
2219 description
=> "Target format for file storage.",
2223 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2228 description
=> "Create a full copy of all disk. This is always done when " .
2229 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2232 target
=> get_standard_option
('pve-node', {
2233 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2244 my $rpcenv = PVE
::RPCEnvironment
::get
();
2246 my $authuser = $rpcenv->get_user();
2248 my $node = extract_param
($param, 'node');
2250 my $vmid = extract_param
($param, 'vmid');
2252 my $newid = extract_param
($param, 'newid');
2254 my $pool = extract_param
($param, 'pool');
2256 if (defined($pool)) {
2257 $rpcenv->check_pool_exist($pool);
2260 my $snapname = extract_param
($param, 'snapname');
2262 my $storage = extract_param
($param, 'storage');
2264 my $format = extract_param
($param, 'format');
2266 my $target = extract_param
($param, 'target');
2268 my $localnode = PVE
::INotify
::nodename
();
2270 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2272 PVE
::Cluster
::check_node_exists
($target) if $target;
2274 my $storecfg = PVE
::Storage
::config
();
2277 # check if storage is enabled on local node
2278 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2280 # check if storage is available on target node
2281 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2282 # clone only works if target storage is shared
2283 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2284 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2288 PVE
::Cluster
::check_cfs_quorum
();
2290 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2292 # exclusive lock if VM is running - else shared lock is enough;
2293 my $shared_lock = $running ?
0 : 1;
2297 # do all tests after lock
2298 # we also try to do all tests before we fork the worker
2300 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2302 PVE
::QemuConfig-
>check_lock($conf);
2304 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2306 die "unexpected state change\n" if $verify_running != $running;
2308 die "snapshot '$snapname' does not exist\n"
2309 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2311 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2313 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2315 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2317 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2319 die "unable to create VM $newid: config file already exists\n"
2322 my $newconf = { lock => 'clone' };
2327 foreach my $opt (keys %$oldconf) {
2328 my $value = $oldconf->{$opt};
2330 # do not copy snapshot related info
2331 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2332 $opt eq 'vmstate' || $opt eq 'snapstate';
2334 # no need to copy unused images, because VMID(owner) changes anyways
2335 next if $opt =~ m/^unused\d+$/;
2337 # always change MAC! address
2338 if ($opt =~ m/^net(\d+)$/) {
2339 my $net = PVE
::QemuServer
::parse_net
($value);
2340 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2341 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2342 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2343 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2344 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2345 die "unable to parse drive options for '$opt'\n" if !$drive;
2346 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2347 $newconf->{$opt} = $value; # simply copy configuration
2349 if ($param->{full
}) {
2350 die "Full clone feature is not available"
2351 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2352 $fullclone->{$opt} = 1;
2354 # not full means clone instead of copy
2355 die "Linked clone feature is not available"
2356 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2358 $drives->{$opt} = $drive;
2359 push @$vollist, $drive->{file
};
2362 # copy everything else
2363 $newconf->{$opt} = $value;
2367 # auto generate a new uuid
2368 my ($uuid, $uuid_str);
2369 UUID
::generate
($uuid);
2370 UUID
::unparse
($uuid, $uuid_str);
2371 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2372 $smbios1->{uuid
} = $uuid_str;
2373 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2375 delete $newconf->{template
};
2377 if ($param->{name
}) {
2378 $newconf->{name
} = $param->{name
};
2380 if ($oldconf->{name
}) {
2381 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2383 $newconf->{name
} = "Copy-of-VM-$vmid";
2387 if ($param->{description
}) {
2388 $newconf->{description
} = $param->{description
};
2391 # create empty/temp config - this fails if VM already exists on other node
2392 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2397 my $newvollist = [];
2400 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2402 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2404 foreach my $opt (keys %$drives) {
2405 my $drive = $drives->{$opt};
2407 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2408 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2410 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2412 PVE
::QemuConfig-
>write_config($newid, $newconf);
2415 delete $newconf->{lock};
2416 PVE
::QemuConfig-
>write_config($newid, $newconf);
2419 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2420 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2421 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2423 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2424 die "Failed to move config to node '$target' - rename failed: $!\n"
2425 if !rename($conffile, $newconffile);
2428 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2433 sleep 1; # some storage like rbd need to wait before release volume - really?
2435 foreach my $volid (@$newvollist) {
2436 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2439 die "clone failed: $err";
2445 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2447 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2450 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2451 # Aquire exclusive lock lock for $newid
2452 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2457 __PACKAGE__-
>register_method({
2458 name
=> 'move_vm_disk',
2459 path
=> '{vmid}/move_disk',
2463 description
=> "Move volume to different storage.",
2465 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2466 "and 'Datastore.AllocateSpace' permissions on the storage.",
2469 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2470 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2474 additionalProperties
=> 0,
2476 node
=> get_standard_option
('pve-node'),
2477 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2480 description
=> "The disk you want to move.",
2481 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2483 storage
=> get_standard_option
('pve-storage-id', {
2484 description
=> "Target storage.",
2485 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2489 description
=> "Target Format.",
2490 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2495 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2501 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2509 description
=> "the task ID.",
2514 my $rpcenv = PVE
::RPCEnvironment
::get
();
2516 my $authuser = $rpcenv->get_user();
2518 my $node = extract_param
($param, 'node');
2520 my $vmid = extract_param
($param, 'vmid');
2522 my $digest = extract_param
($param, 'digest');
2524 my $disk = extract_param
($param, 'disk');
2526 my $storeid = extract_param
($param, 'storage');
2528 my $format = extract_param
($param, 'format');
2530 my $storecfg = PVE
::Storage
::config
();
2532 my $updatefn = sub {
2534 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2536 PVE
::QemuConfig-
>check_lock($conf);
2538 die "checksum missmatch (file change by other user?)\n"
2539 if $digest && $digest ne $conf->{digest
};
2541 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2543 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2545 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2547 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2550 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2551 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2555 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2556 (!$format || !$oldfmt || $oldfmt eq $format);
2558 # this only checks snapshots because $disk is passed!
2559 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2560 die "you can't move a disk with snapshots and delete the source\n"
2561 if $snapshotted && $param->{delete};
2563 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2565 my $running = PVE
::QemuServer
::check_running
($vmid);
2567 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2571 my $newvollist = [];
2574 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2576 warn "moving disk with snapshots, snapshots will not be moved!\n"
2579 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2580 $vmid, $storeid, $format, 1, $newvollist);
2582 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2584 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2586 PVE
::QemuConfig-
>write_config($vmid, $conf);
2589 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2590 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2597 foreach my $volid (@$newvollist) {
2598 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2601 die "storage migration failed: $err";
2604 if ($param->{delete}) {
2606 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2607 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2613 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2616 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2619 __PACKAGE__-
>register_method({
2620 name
=> 'migrate_vm',
2621 path
=> '{vmid}/migrate',
2625 description
=> "Migrate virtual machine. Creates a new migration task.",
2627 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2630 additionalProperties
=> 0,
2632 node
=> get_standard_option
('pve-node'),
2633 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2634 target
=> get_standard_option
('pve-node', {
2635 description
=> "Target node.",
2636 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2640 description
=> "Use online/live migration.",
2645 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2652 description
=> "the task ID.",
2657 my $rpcenv = PVE
::RPCEnvironment
::get
();
2659 my $authuser = $rpcenv->get_user();
2661 my $target = extract_param
($param, 'target');
2663 my $localnode = PVE
::INotify
::nodename
();
2664 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2666 PVE
::Cluster
::check_cfs_quorum
();
2668 PVE
::Cluster
::check_node_exists
($target);
2670 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2672 my $vmid = extract_param
($param, 'vmid');
2674 raise_param_exc
({ force
=> "Only root may use this option." })
2675 if $param->{force
} && $authuser ne 'root@pam';
2678 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2680 # try to detect errors early
2682 PVE
::QemuConfig-
>check_lock($conf);
2684 if (PVE
::QemuServer
::check_running
($vmid)) {
2685 die "cant migrate running VM without --online\n"
2686 if !$param->{online
};
2689 my $storecfg = PVE
::Storage
::config
();
2690 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2692 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2697 my $service = "vm:$vmid";
2699 my $cmd = ['ha-manager', 'migrate', $service, $target];
2701 print "Executing HA migrate for VM $vmid to node $target\n";
2703 PVE
::Tools
::run_command
($cmd);
2708 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2715 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2718 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2723 __PACKAGE__-
>register_method({
2725 path
=> '{vmid}/monitor',
2729 description
=> "Execute Qemu monitor commands.",
2731 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2734 additionalProperties
=> 0,
2736 node
=> get_standard_option
('pve-node'),
2737 vmid
=> get_standard_option
('pve-vmid'),
2740 description
=> "The monitor command.",
2744 returns
=> { type
=> 'string'},
2748 my $vmid = $param->{vmid
};
2750 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2754 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2756 $res = "ERROR: $@" if $@;
2761 __PACKAGE__-
>register_method({
2762 name
=> 'resize_vm',
2763 path
=> '{vmid}/resize',
2767 description
=> "Extend volume size.",
2769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2772 additionalProperties
=> 0,
2774 node
=> get_standard_option
('pve-node'),
2775 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2776 skiplock
=> get_standard_option
('skiplock'),
2779 description
=> "The disk you want to resize.",
2780 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2784 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2785 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.",
2789 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2795 returns
=> { type
=> 'null'},
2799 my $rpcenv = PVE
::RPCEnvironment
::get
();
2801 my $authuser = $rpcenv->get_user();
2803 my $node = extract_param
($param, 'node');
2805 my $vmid = extract_param
($param, 'vmid');
2807 my $digest = extract_param
($param, 'digest');
2809 my $disk = extract_param
($param, 'disk');
2811 my $sizestr = extract_param
($param, 'size');
2813 my $skiplock = extract_param
($param, 'skiplock');
2814 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2815 if $skiplock && $authuser ne 'root@pam';
2817 my $storecfg = PVE
::Storage
::config
();
2819 my $updatefn = sub {
2821 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2823 die "checksum missmatch (file change by other user?)\n"
2824 if $digest && $digest ne $conf->{digest
};
2825 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2827 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2829 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2831 my (undef, undef, undef, undef, undef, undef, $format) =
2832 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2834 die "can't resize volume: $disk if snapshot exists\n"
2835 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2837 my $volid = $drive->{file
};
2839 die "disk '$disk' has no associated volume\n" if !$volid;
2841 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2843 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2845 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2847 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2848 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2850 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2851 my ($ext, $newsize, $unit) = ($1, $2, $4);
2854 $newsize = $newsize * 1024;
2855 } elsif ($unit eq 'M') {
2856 $newsize = $newsize * 1024 * 1024;
2857 } elsif ($unit eq 'G') {
2858 $newsize = $newsize * 1024 * 1024 * 1024;
2859 } elsif ($unit eq 'T') {
2860 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2863 $newsize += $size if $ext;
2864 $newsize = int($newsize);
2866 die "unable to skrink disk size\n" if $newsize < $size;
2868 return if $size == $newsize;
2870 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2872 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2874 $drive->{size
} = $newsize;
2875 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2877 PVE
::QemuConfig-
>write_config($vmid, $conf);
2880 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2884 __PACKAGE__-
>register_method({
2885 name
=> 'snapshot_list',
2886 path
=> '{vmid}/snapshot',
2888 description
=> "List all snapshots.",
2890 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2893 protected
=> 1, # qemu pid files are only readable by root
2895 additionalProperties
=> 0,
2897 vmid
=> get_standard_option
('pve-vmid'),
2898 node
=> get_standard_option
('pve-node'),
2907 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2912 my $vmid = $param->{vmid
};
2914 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2915 my $snaphash = $conf->{snapshots
} || {};
2919 foreach my $name (keys %$snaphash) {
2920 my $d = $snaphash->{$name};
2923 snaptime
=> $d->{snaptime
} || 0,
2924 vmstate
=> $d->{vmstate
} ?
1 : 0,
2925 description
=> $d->{description
} || '',
2927 $item->{parent
} = $d->{parent
} if $d->{parent
};
2928 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2932 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2933 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2934 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2936 push @$res, $current;
2941 __PACKAGE__-
>register_method({
2943 path
=> '{vmid}/snapshot',
2947 description
=> "Snapshot a VM.",
2949 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2952 additionalProperties
=> 0,
2954 node
=> get_standard_option
('pve-node'),
2955 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2956 snapname
=> get_standard_option
('pve-snapshot-name'),
2960 description
=> "Save the vmstate",
2965 description
=> "A textual description or comment.",
2971 description
=> "the task ID.",
2976 my $rpcenv = PVE
::RPCEnvironment
::get
();
2978 my $authuser = $rpcenv->get_user();
2980 my $node = extract_param
($param, 'node');
2982 my $vmid = extract_param
($param, 'vmid');
2984 my $snapname = extract_param
($param, 'snapname');
2986 die "unable to use snapshot name 'current' (reserved name)\n"
2987 if $snapname eq 'current';
2990 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2991 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
2992 $param->{description
});
2995 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2998 __PACKAGE__-
>register_method({
2999 name
=> 'snapshot_cmd_idx',
3000 path
=> '{vmid}/snapshot/{snapname}',
3007 additionalProperties
=> 0,
3009 vmid
=> get_standard_option
('pve-vmid'),
3010 node
=> get_standard_option
('pve-node'),
3011 snapname
=> get_standard_option
('pve-snapshot-name'),
3020 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3027 push @$res, { cmd
=> 'rollback' };
3028 push @$res, { cmd
=> 'config' };
3033 __PACKAGE__-
>register_method({
3034 name
=> 'update_snapshot_config',
3035 path
=> '{vmid}/snapshot/{snapname}/config',
3039 description
=> "Update snapshot metadata.",
3041 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3044 additionalProperties
=> 0,
3046 node
=> get_standard_option
('pve-node'),
3047 vmid
=> get_standard_option
('pve-vmid'),
3048 snapname
=> get_standard_option
('pve-snapshot-name'),
3052 description
=> "A textual description or comment.",
3056 returns
=> { type
=> 'null' },
3060 my $rpcenv = PVE
::RPCEnvironment
::get
();
3062 my $authuser = $rpcenv->get_user();
3064 my $vmid = extract_param
($param, 'vmid');
3066 my $snapname = extract_param
($param, 'snapname');
3068 return undef if !defined($param->{description
});
3070 my $updatefn = sub {
3072 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3074 PVE
::QemuConfig-
>check_lock($conf);
3076 my $snap = $conf->{snapshots
}->{$snapname};
3078 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3080 $snap->{description
} = $param->{description
} if defined($param->{description
});
3082 PVE
::QemuConfig-
>write_config($vmid, $conf);
3085 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3090 __PACKAGE__-
>register_method({
3091 name
=> 'get_snapshot_config',
3092 path
=> '{vmid}/snapshot/{snapname}/config',
3095 description
=> "Get snapshot configuration",
3097 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3100 additionalProperties
=> 0,
3102 node
=> get_standard_option
('pve-node'),
3103 vmid
=> get_standard_option
('pve-vmid'),
3104 snapname
=> get_standard_option
('pve-snapshot-name'),
3107 returns
=> { type
=> "object" },
3111 my $rpcenv = PVE
::RPCEnvironment
::get
();
3113 my $authuser = $rpcenv->get_user();
3115 my $vmid = extract_param
($param, 'vmid');
3117 my $snapname = extract_param
($param, 'snapname');
3119 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3121 my $snap = $conf->{snapshots
}->{$snapname};
3123 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3128 __PACKAGE__-
>register_method({
3130 path
=> '{vmid}/snapshot/{snapname}/rollback',
3134 description
=> "Rollback VM state to specified snapshot.",
3136 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3139 additionalProperties
=> 0,
3141 node
=> get_standard_option
('pve-node'),
3142 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3143 snapname
=> get_standard_option
('pve-snapshot-name'),
3148 description
=> "the task ID.",
3153 my $rpcenv = PVE
::RPCEnvironment
::get
();
3155 my $authuser = $rpcenv->get_user();
3157 my $node = extract_param
($param, 'node');
3159 my $vmid = extract_param
($param, 'vmid');
3161 my $snapname = extract_param
($param, 'snapname');
3164 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3165 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3168 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3171 __PACKAGE__-
>register_method({
3172 name
=> 'delsnapshot',
3173 path
=> '{vmid}/snapshot/{snapname}',
3177 description
=> "Delete a VM snapshot.",
3179 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3182 additionalProperties
=> 0,
3184 node
=> get_standard_option
('pve-node'),
3185 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3186 snapname
=> get_standard_option
('pve-snapshot-name'),
3190 description
=> "For removal from config file, even if removing disk snapshots fails.",
3196 description
=> "the task ID.",
3201 my $rpcenv = PVE
::RPCEnvironment
::get
();
3203 my $authuser = $rpcenv->get_user();
3205 my $node = extract_param
($param, 'node');
3207 my $vmid = extract_param
($param, 'vmid');
3209 my $snapname = extract_param
($param, 'snapname');
3212 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3213 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3216 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3219 __PACKAGE__-
>register_method({
3221 path
=> '{vmid}/template',
3225 description
=> "Create a Template.",
3227 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3228 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3231 additionalProperties
=> 0,
3233 node
=> get_standard_option
('pve-node'),
3234 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3238 description
=> "If you want to convert only 1 disk to base image.",
3239 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3244 returns
=> { type
=> 'null'},
3248 my $rpcenv = PVE
::RPCEnvironment
::get
();
3250 my $authuser = $rpcenv->get_user();
3252 my $node = extract_param
($param, 'node');
3254 my $vmid = extract_param
($param, 'vmid');
3256 my $disk = extract_param
($param, 'disk');
3258 my $updatefn = sub {
3260 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3262 PVE
::QemuConfig-
>check_lock($conf);
3264 die "unable to create template, because VM contains snapshots\n"
3265 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3267 die "you can't convert a template to a template\n"
3268 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3270 die "you can't convert a VM to template if VM is running\n"
3271 if PVE
::QemuServer
::check_running
($vmid);
3274 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3277 $conf->{template
} = 1;
3278 PVE
::QemuConfig-
>write_config($vmid, $conf);
3280 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3283 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);