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 - already existing on cluster node '$current_node'\n";
499 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
502 unique
=> $unique });
504 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
507 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
513 PVE
::Cluster
::check_vmid_unused
($vmid);
523 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
525 # try to be smart about bootdisk
526 my @disks = PVE
::QemuServer
::valid_drive_names
();
528 foreach my $ds (reverse @disks) {
529 next if !$conf->{$ds};
530 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
531 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
535 if (!$conf->{bootdisk
} && $firstdisk) {
536 $conf->{bootdisk
} = $firstdisk;
539 # auto generate uuid if user did not specify smbios1 option
540 if (!$conf->{smbios1
}) {
541 my ($uuid, $uuid_str);
542 UUID
::generate
($uuid);
543 UUID
::unparse
($uuid, $uuid_str);
544 $conf->{smbios1
} = "uuid=$uuid_str";
547 PVE
::QemuConfig-
>write_config($vmid, $conf);
553 foreach my $volid (@$vollist) {
554 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
557 die "create failed - $err";
560 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
563 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
566 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
569 __PACKAGE__-
>register_method({
574 description
=> "Directory index",
579 additionalProperties
=> 0,
581 node
=> get_standard_option
('pve-node'),
582 vmid
=> get_standard_option
('pve-vmid'),
590 subdir
=> { type
=> 'string' },
593 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
599 { subdir
=> 'config' },
600 { subdir
=> 'pending' },
601 { subdir
=> 'status' },
602 { subdir
=> 'unlink' },
603 { subdir
=> 'vncproxy' },
604 { subdir
=> 'migrate' },
605 { subdir
=> 'resize' },
606 { subdir
=> 'move' },
608 { subdir
=> 'rrddata' },
609 { subdir
=> 'monitor' },
610 { subdir
=> 'snapshot' },
611 { subdir
=> 'spiceproxy' },
612 { subdir
=> 'sendkey' },
613 { subdir
=> 'firewall' },
619 __PACKAGE__-
>register_method ({
620 subclass
=> "PVE::API2::Firewall::VM",
621 path
=> '{vmid}/firewall',
624 __PACKAGE__-
>register_method({
626 path
=> '{vmid}/rrd',
628 protected
=> 1, # fixme: can we avoid that?
630 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
632 description
=> "Read VM RRD statistics (returns PNG)",
634 additionalProperties
=> 0,
636 node
=> get_standard_option
('pve-node'),
637 vmid
=> get_standard_option
('pve-vmid'),
639 description
=> "Specify the time frame you are interested in.",
641 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
644 description
=> "The list of datasources you want to display.",
645 type
=> 'string', format
=> 'pve-configid-list',
648 description
=> "The RRD consolidation function",
650 enum
=> [ 'AVERAGE', 'MAX' ],
658 filename
=> { type
=> 'string' },
664 return PVE
::Cluster
::create_rrd_graph
(
665 "pve2-vm/$param->{vmid}", $param->{timeframe
},
666 $param->{ds
}, $param->{cf
});
670 __PACKAGE__-
>register_method({
672 path
=> '{vmid}/rrddata',
674 protected
=> 1, # fixme: can we avoid that?
676 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
678 description
=> "Read VM RRD statistics",
680 additionalProperties
=> 0,
682 node
=> get_standard_option
('pve-node'),
683 vmid
=> get_standard_option
('pve-vmid'),
685 description
=> "Specify the time frame you are interested in.",
687 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
690 description
=> "The RRD consolidation function",
692 enum
=> [ 'AVERAGE', 'MAX' ],
707 return PVE
::Cluster
::create_rrd_data
(
708 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
712 __PACKAGE__-
>register_method({
714 path
=> '{vmid}/config',
717 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
719 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
722 additionalProperties
=> 0,
724 node
=> get_standard_option
('pve-node'),
725 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
727 description
=> "Get current values (instead of pending values).",
739 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
746 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
748 delete $conf->{snapshots
};
750 if (!$param->{current
}) {
751 foreach my $opt (keys %{$conf->{pending
}}) {
752 next if $opt eq 'delete';
753 my $value = $conf->{pending
}->{$opt};
754 next if ref($value); # just to be sure
755 $conf->{$opt} = $value;
757 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
758 foreach my $opt (keys %$pending_delete_hash) {
759 delete $conf->{$opt} if $conf->{$opt};
763 delete $conf->{pending
};
768 __PACKAGE__-
>register_method({
769 name
=> 'vm_pending',
770 path
=> '{vmid}/pending',
773 description
=> "Get virtual machine configuration, including pending changes.",
775 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
778 additionalProperties
=> 0,
780 node
=> get_standard_option
('pve-node'),
781 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
790 description
=> "Configuration option name.",
794 description
=> "Current value.",
799 description
=> "Pending value.",
804 description
=> "Indicates a pending delete request if present and not 0. " .
805 "The value 2 indicates a force-delete request.",
817 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
819 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
823 foreach my $opt (keys %$conf) {
824 next if ref($conf->{$opt});
825 my $item = { key
=> $opt };
826 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
827 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
828 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
832 foreach my $opt (keys %{$conf->{pending
}}) {
833 next if $opt eq 'delete';
834 next if ref($conf->{pending
}->{$opt}); # just to be sure
835 next if defined($conf->{$opt});
836 my $item = { key
=> $opt };
837 $item->{pending
} = $conf->{pending
}->{$opt};
841 while (my ($opt, $force) = each %$pending_delete_hash) {
842 next if $conf->{pending
}->{$opt}; # just to be sure
843 next if $conf->{$opt};
844 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
851 # POST/PUT {vmid}/config implementation
853 # The original API used PUT (idempotent) an we assumed that all operations
854 # are fast. But it turned out that almost any configuration change can
855 # involve hot-plug actions, or disk alloc/free. Such actions can take long
856 # time to complete and have side effects (not idempotent).
858 # The new implementation uses POST and forks a worker process. We added
859 # a new option 'background_delay'. If specified we wait up to
860 # 'background_delay' second for the worker task to complete. It returns null
861 # if the task is finished within that time, else we return the UPID.
863 my $update_vm_api = sub {
864 my ($param, $sync) = @_;
866 my $rpcenv = PVE
::RPCEnvironment
::get
();
868 my $authuser = $rpcenv->get_user();
870 my $node = extract_param
($param, 'node');
872 my $vmid = extract_param
($param, 'vmid');
874 my $digest = extract_param
($param, 'digest');
876 my $background_delay = extract_param
($param, 'background_delay');
878 my @paramarr = (); # used for log message
879 foreach my $key (keys %$param) {
880 push @paramarr, "-$key", $param->{$key};
883 my $skiplock = extract_param
($param, 'skiplock');
884 raise_param_exc
({ skiplock
=> "Only root may use this option." })
885 if $skiplock && $authuser ne 'root@pam';
887 my $delete_str = extract_param
($param, 'delete');
889 my $revert_str = extract_param
($param, 'revert');
891 my $force = extract_param
($param, 'force');
893 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
895 my $storecfg = PVE
::Storage
::config
();
897 my $defaults = PVE
::QemuServer
::load_defaults
();
899 &$resolve_cdrom_alias($param);
901 # now try to verify all parameters
904 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
905 if (!PVE
::QemuServer
::option_exists
($opt)) {
906 raise_param_exc
({ revert
=> "unknown option '$opt'" });
909 raise_param_exc
({ delete => "you can't use '-$opt' and " .
910 "-revert $opt' at the same time" })
911 if defined($param->{$opt});
917 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
918 $opt = 'ide2' if $opt eq 'cdrom';
920 raise_param_exc
({ delete => "you can't use '-$opt' and " .
921 "-delete $opt' at the same time" })
922 if defined($param->{$opt});
924 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
925 "-revert $opt' at the same time" })
928 if (!PVE
::QemuServer
::option_exists
($opt)) {
929 raise_param_exc
({ delete => "unknown option '$opt'" });
935 foreach my $opt (keys %$param) {
936 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
938 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
939 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
940 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
941 } elsif ($opt =~ m/^net(\d+)$/) {
943 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
944 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
948 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
950 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
952 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
956 my $conf = PVE
::QemuConfig-
>load_config($vmid);
958 die "checksum missmatch (file change by other user?)\n"
959 if $digest && $digest ne $conf->{digest
};
961 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
963 foreach my $opt (keys %$revert) {
964 if (defined($conf->{$opt})) {
965 $param->{$opt} = $conf->{$opt};
966 } elsif (defined($conf->{pending
}->{$opt})) {
971 if ($param->{memory
} || defined($param->{balloon
})) {
972 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
973 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
975 die "balloon value too large (must be smaller than assigned memory)\n"
976 if $balloon && $balloon > $maxmem;
979 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
983 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
985 # write updates to pending section
987 my $modified = {}; # record what $option we modify
989 foreach my $opt (@delete) {
990 $modified->{$opt} = 1;
991 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
992 if ($opt =~ m/^unused/) {
993 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
994 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
995 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
996 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
997 delete $conf->{$opt};
998 PVE
::QemuConfig-
>write_config($vmid, $conf);
1000 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1001 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1002 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1003 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1004 if defined($conf->{pending
}->{$opt});
1005 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1006 PVE
::QemuConfig-
>write_config($vmid, $conf);
1008 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1009 PVE
::QemuConfig-
>write_config($vmid, $conf);
1013 foreach my $opt (keys %$param) { # add/change
1014 $modified->{$opt} = 1;
1015 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1016 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1018 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1019 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1020 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1021 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1023 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1025 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1026 if defined($conf->{pending
}->{$opt});
1028 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1030 $conf->{pending
}->{$opt} = $param->{$opt};
1032 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1033 PVE
::QemuConfig-
>write_config($vmid, $conf);
1036 # remove pending changes when nothing changed
1037 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1038 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1039 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1041 return if !scalar(keys %{$conf->{pending
}});
1043 my $running = PVE
::QemuServer
::check_running
($vmid);
1045 # apply pending changes
1047 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1051 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1052 raise_param_exc
($errors) if scalar(keys %$errors);
1054 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1064 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1066 if ($background_delay) {
1068 # Note: It would be better to do that in the Event based HTTPServer
1069 # to avoid blocking call to sleep.
1071 my $end_time = time() + $background_delay;
1073 my $task = PVE
::Tools
::upid_decode
($upid);
1076 while (time() < $end_time) {
1077 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1079 sleep(1); # this gets interrupted when child process ends
1083 my $status = PVE
::Tools
::upid_read_status
($upid);
1084 return undef if $status eq 'OK';
1093 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1096 my $vm_config_perm_list = [
1101 'VM.Config.Network',
1103 'VM.Config.Options',
1106 __PACKAGE__-
>register_method({
1107 name
=> 'update_vm_async',
1108 path
=> '{vmid}/config',
1112 description
=> "Set virtual machine options (asynchrounous API).",
1114 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1117 additionalProperties
=> 0,
1118 properties
=> PVE
::QemuServer
::json_config_properties
(
1120 node
=> get_standard_option
('pve-node'),
1121 vmid
=> get_standard_option
('pve-vmid'),
1122 skiplock
=> get_standard_option
('skiplock'),
1124 type
=> 'string', format
=> 'pve-configid-list',
1125 description
=> "A list of settings you want to delete.",
1129 type
=> 'string', format
=> 'pve-configid-list',
1130 description
=> "Revert a pending change.",
1135 description
=> $opt_force_description,
1137 requires
=> 'delete',
1141 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1145 background_delay
=> {
1147 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1158 code
=> $update_vm_api,
1161 __PACKAGE__-
>register_method({
1162 name
=> 'update_vm',
1163 path
=> '{vmid}/config',
1167 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1169 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1172 additionalProperties
=> 0,
1173 properties
=> PVE
::QemuServer
::json_config_properties
(
1175 node
=> get_standard_option
('pve-node'),
1176 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1177 skiplock
=> get_standard_option
('skiplock'),
1179 type
=> 'string', format
=> 'pve-configid-list',
1180 description
=> "A list of settings you want to delete.",
1184 type
=> 'string', format
=> 'pve-configid-list',
1185 description
=> "Revert a pending change.",
1190 description
=> $opt_force_description,
1192 requires
=> 'delete',
1196 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1202 returns
=> { type
=> 'null' },
1205 &$update_vm_api($param, 1);
1211 __PACKAGE__-
>register_method({
1212 name
=> 'destroy_vm',
1217 description
=> "Destroy the vm (also delete all used/owned volumes).",
1219 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1222 additionalProperties
=> 0,
1224 node
=> get_standard_option
('pve-node'),
1225 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1226 skiplock
=> get_standard_option
('skiplock'),
1235 my $rpcenv = PVE
::RPCEnvironment
::get
();
1237 my $authuser = $rpcenv->get_user();
1239 my $vmid = $param->{vmid
};
1241 my $skiplock = $param->{skiplock
};
1242 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1243 if $skiplock && $authuser ne 'root@pam';
1246 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1248 my $storecfg = PVE
::Storage
::config
();
1250 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1252 die "unable to remove VM $vmid - used in HA resources\n"
1253 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1255 # early tests (repeat after locking)
1256 die "VM $vmid is running - destroy failed\n"
1257 if PVE
::QemuServer
::check_running
($vmid);
1262 syslog
('info', "destroy VM $vmid: $upid\n");
1264 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1266 PVE
::AccessControl
::remove_vm_access
($vmid);
1268 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1271 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1274 __PACKAGE__-
>register_method({
1276 path
=> '{vmid}/unlink',
1280 description
=> "Unlink/delete disk images.",
1282 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1285 additionalProperties
=> 0,
1287 node
=> get_standard_option
('pve-node'),
1288 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1290 type
=> 'string', format
=> 'pve-configid-list',
1291 description
=> "A list of disk IDs you want to delete.",
1295 description
=> $opt_force_description,
1300 returns
=> { type
=> 'null'},
1304 $param->{delete} = extract_param
($param, 'idlist');
1306 __PACKAGE__-
>update_vm($param);
1313 __PACKAGE__-
>register_method({
1315 path
=> '{vmid}/vncproxy',
1319 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1321 description
=> "Creates a TCP VNC proxy connections.",
1323 additionalProperties
=> 0,
1325 node
=> get_standard_option
('pve-node'),
1326 vmid
=> get_standard_option
('pve-vmid'),
1330 description
=> "starts websockify instead of vncproxy",
1335 additionalProperties
=> 0,
1337 user
=> { type
=> 'string' },
1338 ticket
=> { type
=> 'string' },
1339 cert
=> { type
=> 'string' },
1340 port
=> { type
=> 'integer' },
1341 upid
=> { type
=> 'string' },
1347 my $rpcenv = PVE
::RPCEnvironment
::get
();
1349 my $authuser = $rpcenv->get_user();
1351 my $vmid = $param->{vmid
};
1352 my $node = $param->{node
};
1353 my $websocket = $param->{websocket
};
1355 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1357 my $authpath = "/vms/$vmid";
1359 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1361 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1364 my ($remip, $family);
1367 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1368 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1369 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1370 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1372 $family = PVE
::Tools
::get_host_address_family
($node);
1375 my $port = PVE
::Tools
::next_vnc_port
($family);
1382 syslog
('info', "starting vnc proxy $upid\n");
1386 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1388 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1390 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1391 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1392 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1393 '-timeout', $timeout, '-authpath', $authpath,
1394 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1397 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1399 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1401 my $qmstr = join(' ', @$qmcmd);
1403 # also redirect stderr (else we get RFB protocol errors)
1404 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1407 PVE
::Tools
::run_command
($cmd);
1412 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1414 PVE
::Tools
::wait_for_vnc_port
($port);
1425 __PACKAGE__-
>register_method({
1426 name
=> 'vncwebsocket',
1427 path
=> '{vmid}/vncwebsocket',
1430 description
=> "You also need to pass a valid ticket (vncticket).",
1431 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1433 description
=> "Opens a weksocket for VNC traffic.",
1435 additionalProperties
=> 0,
1437 node
=> get_standard_option
('pve-node'),
1438 vmid
=> get_standard_option
('pve-vmid'),
1440 description
=> "Ticket from previous call to vncproxy.",
1445 description
=> "Port number returned by previous vncproxy call.",
1455 port
=> { type
=> 'string' },
1461 my $rpcenv = PVE
::RPCEnvironment
::get
();
1463 my $authuser = $rpcenv->get_user();
1465 my $vmid = $param->{vmid
};
1466 my $node = $param->{node
};
1468 my $authpath = "/vms/$vmid";
1470 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1472 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1474 # Note: VNC ports are acessible from outside, so we do not gain any
1475 # security if we verify that $param->{port} belongs to VM $vmid. This
1476 # check is done by verifying the VNC ticket (inside VNC protocol).
1478 my $port = $param->{port
};
1480 return { port
=> $port };
1483 __PACKAGE__-
>register_method({
1484 name
=> 'spiceproxy',
1485 path
=> '{vmid}/spiceproxy',
1490 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1492 description
=> "Returns a SPICE configuration to connect to the VM.",
1494 additionalProperties
=> 0,
1496 node
=> get_standard_option
('pve-node'),
1497 vmid
=> get_standard_option
('pve-vmid'),
1498 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1501 returns
=> get_standard_option
('remote-viewer-config'),
1505 my $rpcenv = PVE
::RPCEnvironment
::get
();
1507 my $authuser = $rpcenv->get_user();
1509 my $vmid = $param->{vmid
};
1510 my $node = $param->{node
};
1511 my $proxy = $param->{proxy
};
1513 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1514 my $title = "VM $vmid";
1515 $title .= " - ". $conf->{name
} if $conf->{name
};
1517 my $port = PVE
::QemuServer
::spice_port
($vmid);
1519 my ($ticket, undef, $remote_viewer_config) =
1520 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1522 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1523 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1525 return $remote_viewer_config;
1528 __PACKAGE__-
>register_method({
1530 path
=> '{vmid}/status',
1533 description
=> "Directory index",
1538 additionalProperties
=> 0,
1540 node
=> get_standard_option
('pve-node'),
1541 vmid
=> get_standard_option
('pve-vmid'),
1549 subdir
=> { type
=> 'string' },
1552 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1558 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1561 { subdir
=> 'current' },
1562 { subdir
=> 'start' },
1563 { subdir
=> 'stop' },
1569 __PACKAGE__-
>register_method({
1570 name
=> 'vm_status',
1571 path
=> '{vmid}/status/current',
1574 protected
=> 1, # qemu pid files are only readable by root
1575 description
=> "Get virtual machine status.",
1577 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1580 additionalProperties
=> 0,
1582 node
=> get_standard_option
('pve-node'),
1583 vmid
=> get_standard_option
('pve-vmid'),
1586 returns
=> { type
=> 'object' },
1591 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1593 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1594 my $status = $vmstatus->{$param->{vmid
}};
1596 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1598 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1603 __PACKAGE__-
>register_method({
1605 path
=> '{vmid}/status/start',
1609 description
=> "Start virtual machine.",
1611 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1614 additionalProperties
=> 0,
1616 node
=> get_standard_option
('pve-node'),
1617 vmid
=> get_standard_option
('pve-vmid',
1618 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1619 skiplock
=> get_standard_option
('skiplock'),
1620 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1621 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1622 machine
=> get_standard_option
('pve-qm-machine'),
1631 my $rpcenv = PVE
::RPCEnvironment
::get
();
1633 my $authuser = $rpcenv->get_user();
1635 my $node = extract_param
($param, 'node');
1637 my $vmid = extract_param
($param, 'vmid');
1639 my $machine = extract_param
($param, 'machine');
1641 my $stateuri = extract_param
($param, 'stateuri');
1642 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1643 if $stateuri && $authuser ne 'root@pam';
1645 my $skiplock = extract_param
($param, 'skiplock');
1646 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1647 if $skiplock && $authuser ne 'root@pam';
1649 my $migratedfrom = extract_param
($param, 'migratedfrom');
1650 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1651 if $migratedfrom && $authuser ne 'root@pam';
1653 # read spice ticket from STDIN
1655 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1656 if (defined(my $line = <>)) {
1658 $spice_ticket = $line;
1662 PVE
::Cluster
::check_cfs_quorum
();
1664 my $storecfg = PVE
::Storage
::config
();
1666 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1667 $rpcenv->{type
} ne 'ha') {
1672 my $service = "vm:$vmid";
1674 my $cmd = ['ha-manager', 'enable', $service];
1676 print "Executing HA start for VM $vmid\n";
1678 PVE
::Tools
::run_command
($cmd);
1683 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1690 syslog
('info', "start VM $vmid: $upid\n");
1692 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1693 $machine, $spice_ticket);
1698 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1702 __PACKAGE__-
>register_method({
1704 path
=> '{vmid}/status/stop',
1708 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1709 "is akin to pulling the power plug of a running computer and may damage the VM data",
1711 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1714 additionalProperties
=> 0,
1716 node
=> get_standard_option
('pve-node'),
1717 vmid
=> get_standard_option
('pve-vmid',
1718 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1719 skiplock
=> get_standard_option
('skiplock'),
1720 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1722 description
=> "Wait maximal timeout seconds.",
1728 description
=> "Do not decativate storage volumes.",
1741 my $rpcenv = PVE
::RPCEnvironment
::get
();
1743 my $authuser = $rpcenv->get_user();
1745 my $node = extract_param
($param, 'node');
1747 my $vmid = extract_param
($param, 'vmid');
1749 my $skiplock = extract_param
($param, 'skiplock');
1750 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1751 if $skiplock && $authuser ne 'root@pam';
1753 my $keepActive = extract_param
($param, 'keepActive');
1754 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1755 if $keepActive && $authuser ne 'root@pam';
1757 my $migratedfrom = extract_param
($param, 'migratedfrom');
1758 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1759 if $migratedfrom && $authuser ne 'root@pam';
1762 my $storecfg = PVE
::Storage
::config
();
1764 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1769 my $service = "vm:$vmid";
1771 my $cmd = ['ha-manager', 'disable', $service];
1773 print "Executing HA stop for VM $vmid\n";
1775 PVE
::Tools
::run_command
($cmd);
1780 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1786 syslog
('info', "stop VM $vmid: $upid\n");
1788 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1789 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1794 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1798 __PACKAGE__-
>register_method({
1800 path
=> '{vmid}/status/reset',
1804 description
=> "Reset virtual machine.",
1806 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1809 additionalProperties
=> 0,
1811 node
=> get_standard_option
('pve-node'),
1812 vmid
=> get_standard_option
('pve-vmid',
1813 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1814 skiplock
=> get_standard_option
('skiplock'),
1823 my $rpcenv = PVE
::RPCEnvironment
::get
();
1825 my $authuser = $rpcenv->get_user();
1827 my $node = extract_param
($param, 'node');
1829 my $vmid = extract_param
($param, 'vmid');
1831 my $skiplock = extract_param
($param, 'skiplock');
1832 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1833 if $skiplock && $authuser ne 'root@pam';
1835 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1840 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1845 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1848 __PACKAGE__-
>register_method({
1849 name
=> 'vm_shutdown',
1850 path
=> '{vmid}/status/shutdown',
1854 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1855 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1857 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1860 additionalProperties
=> 0,
1862 node
=> get_standard_option
('pve-node'),
1863 vmid
=> get_standard_option
('pve-vmid',
1864 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1865 skiplock
=> get_standard_option
('skiplock'),
1867 description
=> "Wait maximal timeout seconds.",
1873 description
=> "Make sure the VM stops.",
1879 description
=> "Do not decativate storage volumes.",
1892 my $rpcenv = PVE
::RPCEnvironment
::get
();
1894 my $authuser = $rpcenv->get_user();
1896 my $node = extract_param
($param, 'node');
1898 my $vmid = extract_param
($param, 'vmid');
1900 my $skiplock = extract_param
($param, 'skiplock');
1901 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1902 if $skiplock && $authuser ne 'root@pam';
1904 my $keepActive = extract_param
($param, 'keepActive');
1905 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1906 if $keepActive && $authuser ne 'root@pam';
1908 my $storecfg = PVE
::Storage
::config
();
1912 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1913 # otherwise, we will infer a shutdown command, but run into the timeout,
1914 # then when the vm is resumed, it will instantly shutdown
1916 # checking the qmp status here to get feedback to the gui/cli/api
1917 # and the status query should not take too long
1920 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1924 if (!$err && $qmpstatus->{status
} eq "paused") {
1925 if ($param->{forceStop
}) {
1926 warn "VM is paused - stop instead of shutdown\n";
1929 die "VM is paused - cannot shutdown\n";
1936 syslog
('info', "shutdown VM $vmid: $upid\n");
1938 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1939 $shutdown, $param->{forceStop
}, $keepActive);
1944 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1947 __PACKAGE__-
>register_method({
1948 name
=> 'vm_suspend',
1949 path
=> '{vmid}/status/suspend',
1953 description
=> "Suspend virtual machine.",
1955 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1958 additionalProperties
=> 0,
1960 node
=> get_standard_option
('pve-node'),
1961 vmid
=> get_standard_option
('pve-vmid',
1962 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1963 skiplock
=> get_standard_option
('skiplock'),
1972 my $rpcenv = PVE
::RPCEnvironment
::get
();
1974 my $authuser = $rpcenv->get_user();
1976 my $node = extract_param
($param, 'node');
1978 my $vmid = extract_param
($param, 'vmid');
1980 my $skiplock = extract_param
($param, 'skiplock');
1981 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1982 if $skiplock && $authuser ne 'root@pam';
1984 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1989 syslog
('info', "suspend VM $vmid: $upid\n");
1991 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1996 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1999 __PACKAGE__-
>register_method({
2000 name
=> 'vm_resume',
2001 path
=> '{vmid}/status/resume',
2005 description
=> "Resume virtual machine.",
2007 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2010 additionalProperties
=> 0,
2012 node
=> get_standard_option
('pve-node'),
2013 vmid
=> get_standard_option
('pve-vmid',
2014 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2015 skiplock
=> get_standard_option
('skiplock'),
2016 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2026 my $rpcenv = PVE
::RPCEnvironment
::get
();
2028 my $authuser = $rpcenv->get_user();
2030 my $node = extract_param
($param, 'node');
2032 my $vmid = extract_param
($param, 'vmid');
2034 my $skiplock = extract_param
($param, 'skiplock');
2035 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2036 if $skiplock && $authuser ne 'root@pam';
2038 my $nocheck = extract_param
($param, 'nocheck');
2040 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2045 syslog
('info', "resume VM $vmid: $upid\n");
2047 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2052 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2055 __PACKAGE__-
>register_method({
2056 name
=> 'vm_sendkey',
2057 path
=> '{vmid}/sendkey',
2061 description
=> "Send key event to virtual machine.",
2063 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2066 additionalProperties
=> 0,
2068 node
=> get_standard_option
('pve-node'),
2069 vmid
=> get_standard_option
('pve-vmid',
2070 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2071 skiplock
=> get_standard_option
('skiplock'),
2073 description
=> "The key (qemu monitor encoding).",
2078 returns
=> { type
=> 'null'},
2082 my $rpcenv = PVE
::RPCEnvironment
::get
();
2084 my $authuser = $rpcenv->get_user();
2086 my $node = extract_param
($param, 'node');
2088 my $vmid = extract_param
($param, 'vmid');
2090 my $skiplock = extract_param
($param, 'skiplock');
2091 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2092 if $skiplock && $authuser ne 'root@pam';
2094 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2099 __PACKAGE__-
>register_method({
2100 name
=> 'vm_feature',
2101 path
=> '{vmid}/feature',
2105 description
=> "Check if feature for virtual machine is available.",
2107 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2110 additionalProperties
=> 0,
2112 node
=> get_standard_option
('pve-node'),
2113 vmid
=> get_standard_option
('pve-vmid'),
2115 description
=> "Feature to check.",
2117 enum
=> [ 'snapshot', 'clone', 'copy' ],
2119 snapname
=> get_standard_option
('pve-snapshot-name', {
2127 hasFeature
=> { type
=> 'boolean' },
2130 items
=> { type
=> 'string' },
2137 my $node = extract_param
($param, 'node');
2139 my $vmid = extract_param
($param, 'vmid');
2141 my $snapname = extract_param
($param, 'snapname');
2143 my $feature = extract_param
($param, 'feature');
2145 my $running = PVE
::QemuServer
::check_running
($vmid);
2147 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2150 my $snap = $conf->{snapshots
}->{$snapname};
2151 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2154 my $storecfg = PVE
::Storage
::config
();
2156 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2157 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2160 hasFeature
=> $hasFeature,
2161 nodes
=> [ keys %$nodelist ],
2165 __PACKAGE__-
>register_method({
2167 path
=> '{vmid}/clone',
2171 description
=> "Create a copy of virtual machine/template.",
2173 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2174 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2175 "'Datastore.AllocateSpace' on any used storage.",
2178 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2180 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2181 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2186 additionalProperties
=> 0,
2188 node
=> get_standard_option
('pve-node'),
2189 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2190 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2193 type
=> 'string', format
=> 'dns-name',
2194 description
=> "Set a name for the new VM.",
2199 description
=> "Description for the new VM.",
2203 type
=> 'string', format
=> 'pve-poolid',
2204 description
=> "Add the new VM to the specified pool.",
2206 snapname
=> get_standard_option
('pve-snapshot-name', {
2209 storage
=> get_standard_option
('pve-storage-id', {
2210 description
=> "Target storage for full clone.",
2215 description
=> "Target format for file storage.",
2219 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2224 description
=> "Create a full copy of all disk. This is always done when " .
2225 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2228 target
=> get_standard_option
('pve-node', {
2229 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2240 my $rpcenv = PVE
::RPCEnvironment
::get
();
2242 my $authuser = $rpcenv->get_user();
2244 my $node = extract_param
($param, 'node');
2246 my $vmid = extract_param
($param, 'vmid');
2248 my $newid = extract_param
($param, 'newid');
2250 my $pool = extract_param
($param, 'pool');
2252 if (defined($pool)) {
2253 $rpcenv->check_pool_exist($pool);
2256 my $snapname = extract_param
($param, 'snapname');
2258 my $storage = extract_param
($param, 'storage');
2260 my $format = extract_param
($param, 'format');
2262 my $target = extract_param
($param, 'target');
2264 my $localnode = PVE
::INotify
::nodename
();
2266 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2268 PVE
::Cluster
::check_node_exists
($target) if $target;
2270 my $storecfg = PVE
::Storage
::config
();
2273 # check if storage is enabled on local node
2274 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2276 # check if storage is available on target node
2277 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2278 # clone only works if target storage is shared
2279 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2280 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2284 PVE
::Cluster
::check_cfs_quorum
();
2286 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2288 # exclusive lock if VM is running - else shared lock is enough;
2289 my $shared_lock = $running ?
0 : 1;
2293 # do all tests after lock
2294 # we also try to do all tests before we fork the worker
2296 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2298 PVE
::QemuConfig-
>check_lock($conf);
2300 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2302 die "unexpected state change\n" if $verify_running != $running;
2304 die "snapshot '$snapname' does not exist\n"
2305 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2307 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2309 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2311 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2313 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2315 die "unable to create VM $newid: config file already exists\n"
2318 my $newconf = { lock => 'clone' };
2323 foreach my $opt (keys %$oldconf) {
2324 my $value = $oldconf->{$opt};
2326 # do not copy snapshot related info
2327 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2328 $opt eq 'vmstate' || $opt eq 'snapstate';
2330 # no need to copy unused images, because VMID(owner) changes anyways
2331 next if $opt =~ m/^unused\d+$/;
2333 # always change MAC! address
2334 if ($opt =~ m/^net(\d+)$/) {
2335 my $net = PVE
::QemuServer
::parse_net
($value);
2336 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2337 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2338 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2339 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2340 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2341 die "unable to parse drive options for '$opt'\n" if !$drive;
2342 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2343 $newconf->{$opt} = $value; # simply copy configuration
2345 if ($param->{full
}) {
2346 die "Full clone feature is not available"
2347 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2348 $fullclone->{$opt} = 1;
2350 # not full means clone instead of copy
2351 die "Linked clone feature is not available"
2352 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2354 $drives->{$opt} = $drive;
2355 push @$vollist, $drive->{file
};
2358 # copy everything else
2359 $newconf->{$opt} = $value;
2363 # auto generate a new uuid
2364 my ($uuid, $uuid_str);
2365 UUID
::generate
($uuid);
2366 UUID
::unparse
($uuid, $uuid_str);
2367 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2368 $smbios1->{uuid
} = $uuid_str;
2369 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2371 delete $newconf->{template
};
2373 if ($param->{name
}) {
2374 $newconf->{name
} = $param->{name
};
2376 if ($oldconf->{name
}) {
2377 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2379 $newconf->{name
} = "Copy-of-VM-$vmid";
2383 if ($param->{description
}) {
2384 $newconf->{description
} = $param->{description
};
2387 # create empty/temp config - this fails if VM already exists on other node
2388 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2393 my $newvollist = [];
2396 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2398 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2400 foreach my $opt (keys %$drives) {
2401 my $drive = $drives->{$opt};
2403 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2404 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2406 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2408 PVE
::QemuConfig-
>write_config($newid, $newconf);
2411 delete $newconf->{lock};
2412 PVE
::QemuConfig-
>write_config($newid, $newconf);
2415 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2416 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2417 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2419 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2420 die "Failed to move config to node '$target' - rename failed: $!\n"
2421 if !rename($conffile, $newconffile);
2424 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2429 sleep 1; # some storage like rbd need to wait before release volume - really?
2431 foreach my $volid (@$newvollist) {
2432 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2435 die "clone failed: $err";
2441 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2443 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2446 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2447 # Aquire exclusive lock lock for $newid
2448 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2453 __PACKAGE__-
>register_method({
2454 name
=> 'move_vm_disk',
2455 path
=> '{vmid}/move_disk',
2459 description
=> "Move volume to different storage.",
2461 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2462 "and 'Datastore.AllocateSpace' permissions on the storage.",
2465 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2466 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2470 additionalProperties
=> 0,
2472 node
=> get_standard_option
('pve-node'),
2473 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2476 description
=> "The disk you want to move.",
2477 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2479 storage
=> get_standard_option
('pve-storage-id', {
2480 description
=> "Target storage.",
2481 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2485 description
=> "Target Format.",
2486 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2491 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2497 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2505 description
=> "the task ID.",
2510 my $rpcenv = PVE
::RPCEnvironment
::get
();
2512 my $authuser = $rpcenv->get_user();
2514 my $node = extract_param
($param, 'node');
2516 my $vmid = extract_param
($param, 'vmid');
2518 my $digest = extract_param
($param, 'digest');
2520 my $disk = extract_param
($param, 'disk');
2522 my $storeid = extract_param
($param, 'storage');
2524 my $format = extract_param
($param, 'format');
2526 my $storecfg = PVE
::Storage
::config
();
2528 my $updatefn = sub {
2530 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2532 PVE
::QemuConfig-
>check_lock($conf);
2534 die "checksum missmatch (file change by other user?)\n"
2535 if $digest && $digest ne $conf->{digest
};
2537 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2539 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2541 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2543 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2546 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2547 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2551 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2552 (!$format || !$oldfmt || $oldfmt eq $format);
2554 # this only checks snapshots because $disk is passed!
2555 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2556 die "you can't move a disk with snapshots and delete the source\n"
2557 if $snapshotted && $param->{delete};
2559 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2561 my $running = PVE
::QemuServer
::check_running
($vmid);
2563 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2567 my $newvollist = [];
2570 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2572 warn "moving disk with snapshots, snapshots will not be moved!\n"
2575 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2576 $vmid, $storeid, $format, 1, $newvollist);
2578 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2580 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2582 PVE
::QemuConfig-
>write_config($vmid, $conf);
2585 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2586 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2593 foreach my $volid (@$newvollist) {
2594 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2597 die "storage migration failed: $err";
2600 if ($param->{delete}) {
2602 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2603 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2609 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2612 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2615 __PACKAGE__-
>register_method({
2616 name
=> 'migrate_vm',
2617 path
=> '{vmid}/migrate',
2621 description
=> "Migrate virtual machine. Creates a new migration task.",
2623 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2626 additionalProperties
=> 0,
2628 node
=> get_standard_option
('pve-node'),
2629 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2630 target
=> get_standard_option
('pve-node', {
2631 description
=> "Target node.",
2632 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2636 description
=> "Use online/live migration.",
2641 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2648 description
=> "the task ID.",
2653 my $rpcenv = PVE
::RPCEnvironment
::get
();
2655 my $authuser = $rpcenv->get_user();
2657 my $target = extract_param
($param, 'target');
2659 my $localnode = PVE
::INotify
::nodename
();
2660 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2662 PVE
::Cluster
::check_cfs_quorum
();
2664 PVE
::Cluster
::check_node_exists
($target);
2666 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2668 my $vmid = extract_param
($param, 'vmid');
2670 raise_param_exc
({ force
=> "Only root may use this option." })
2671 if $param->{force
} && $authuser ne 'root@pam';
2674 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2676 # try to detect errors early
2678 PVE
::QemuConfig-
>check_lock($conf);
2680 if (PVE
::QemuServer
::check_running
($vmid)) {
2681 die "cant migrate running VM without --online\n"
2682 if !$param->{online
};
2685 my $storecfg = PVE
::Storage
::config
();
2686 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2688 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2693 my $service = "vm:$vmid";
2695 my $cmd = ['ha-manager', 'migrate', $service, $target];
2697 print "Executing HA migrate for VM $vmid to node $target\n";
2699 PVE
::Tools
::run_command
($cmd);
2704 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2711 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2714 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2719 __PACKAGE__-
>register_method({
2721 path
=> '{vmid}/monitor',
2725 description
=> "Execute Qemu monitor commands.",
2727 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2730 additionalProperties
=> 0,
2732 node
=> get_standard_option
('pve-node'),
2733 vmid
=> get_standard_option
('pve-vmid'),
2736 description
=> "The monitor command.",
2740 returns
=> { type
=> 'string'},
2744 my $vmid = $param->{vmid
};
2746 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2750 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2752 $res = "ERROR: $@" if $@;
2757 __PACKAGE__-
>register_method({
2758 name
=> 'resize_vm',
2759 path
=> '{vmid}/resize',
2763 description
=> "Extend volume size.",
2765 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2768 additionalProperties
=> 0,
2770 node
=> get_standard_option
('pve-node'),
2771 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2772 skiplock
=> get_standard_option
('skiplock'),
2775 description
=> "The disk you want to resize.",
2776 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2780 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2781 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.",
2785 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2791 returns
=> { type
=> 'null'},
2795 my $rpcenv = PVE
::RPCEnvironment
::get
();
2797 my $authuser = $rpcenv->get_user();
2799 my $node = extract_param
($param, 'node');
2801 my $vmid = extract_param
($param, 'vmid');
2803 my $digest = extract_param
($param, 'digest');
2805 my $disk = extract_param
($param, 'disk');
2807 my $sizestr = extract_param
($param, 'size');
2809 my $skiplock = extract_param
($param, 'skiplock');
2810 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2811 if $skiplock && $authuser ne 'root@pam';
2813 my $storecfg = PVE
::Storage
::config
();
2815 my $updatefn = sub {
2817 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2819 die "checksum missmatch (file change by other user?)\n"
2820 if $digest && $digest ne $conf->{digest
};
2821 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2823 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2825 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2827 my (undef, undef, undef, undef, undef, undef, $format) =
2828 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2830 die "can't resize volume: $disk if snapshot exists\n"
2831 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2833 my $volid = $drive->{file
};
2835 die "disk '$disk' has no associated volume\n" if !$volid;
2837 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2839 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2841 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2843 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2844 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2846 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2847 my ($ext, $newsize, $unit) = ($1, $2, $4);
2850 $newsize = $newsize * 1024;
2851 } elsif ($unit eq 'M') {
2852 $newsize = $newsize * 1024 * 1024;
2853 } elsif ($unit eq 'G') {
2854 $newsize = $newsize * 1024 * 1024 * 1024;
2855 } elsif ($unit eq 'T') {
2856 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2859 $newsize += $size if $ext;
2860 $newsize = int($newsize);
2862 die "unable to skrink disk size\n" if $newsize < $size;
2864 return if $size == $newsize;
2866 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2868 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2870 $drive->{size
} = $newsize;
2871 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2873 PVE
::QemuConfig-
>write_config($vmid, $conf);
2876 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2880 __PACKAGE__-
>register_method({
2881 name
=> 'snapshot_list',
2882 path
=> '{vmid}/snapshot',
2884 description
=> "List all snapshots.",
2886 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2889 protected
=> 1, # qemu pid files are only readable by root
2891 additionalProperties
=> 0,
2893 vmid
=> get_standard_option
('pve-vmid'),
2894 node
=> get_standard_option
('pve-node'),
2903 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2908 my $vmid = $param->{vmid
};
2910 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2911 my $snaphash = $conf->{snapshots
} || {};
2915 foreach my $name (keys %$snaphash) {
2916 my $d = $snaphash->{$name};
2919 snaptime
=> $d->{snaptime
} || 0,
2920 vmstate
=> $d->{vmstate
} ?
1 : 0,
2921 description
=> $d->{description
} || '',
2923 $item->{parent
} = $d->{parent
} if $d->{parent
};
2924 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2928 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2929 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2930 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2932 push @$res, $current;
2937 __PACKAGE__-
>register_method({
2939 path
=> '{vmid}/snapshot',
2943 description
=> "Snapshot a VM.",
2945 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2948 additionalProperties
=> 0,
2950 node
=> get_standard_option
('pve-node'),
2951 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2952 snapname
=> get_standard_option
('pve-snapshot-name'),
2956 description
=> "Save the vmstate",
2961 description
=> "A textual description or comment.",
2967 description
=> "the task ID.",
2972 my $rpcenv = PVE
::RPCEnvironment
::get
();
2974 my $authuser = $rpcenv->get_user();
2976 my $node = extract_param
($param, 'node');
2978 my $vmid = extract_param
($param, 'vmid');
2980 my $snapname = extract_param
($param, 'snapname');
2982 die "unable to use snapshot name 'current' (reserved name)\n"
2983 if $snapname eq 'current';
2986 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2987 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
2988 $param->{description
});
2991 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2994 __PACKAGE__-
>register_method({
2995 name
=> 'snapshot_cmd_idx',
2996 path
=> '{vmid}/snapshot/{snapname}',
3003 additionalProperties
=> 0,
3005 vmid
=> get_standard_option
('pve-vmid'),
3006 node
=> get_standard_option
('pve-node'),
3007 snapname
=> get_standard_option
('pve-snapshot-name'),
3016 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3023 push @$res, { cmd
=> 'rollback' };
3024 push @$res, { cmd
=> 'config' };
3029 __PACKAGE__-
>register_method({
3030 name
=> 'update_snapshot_config',
3031 path
=> '{vmid}/snapshot/{snapname}/config',
3035 description
=> "Update snapshot metadata.",
3037 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3040 additionalProperties
=> 0,
3042 node
=> get_standard_option
('pve-node'),
3043 vmid
=> get_standard_option
('pve-vmid'),
3044 snapname
=> get_standard_option
('pve-snapshot-name'),
3048 description
=> "A textual description or comment.",
3052 returns
=> { type
=> 'null' },
3056 my $rpcenv = PVE
::RPCEnvironment
::get
();
3058 my $authuser = $rpcenv->get_user();
3060 my $vmid = extract_param
($param, 'vmid');
3062 my $snapname = extract_param
($param, 'snapname');
3064 return undef if !defined($param->{description
});
3066 my $updatefn = sub {
3068 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3070 PVE
::QemuConfig-
>check_lock($conf);
3072 my $snap = $conf->{snapshots
}->{$snapname};
3074 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3076 $snap->{description
} = $param->{description
} if defined($param->{description
});
3078 PVE
::QemuConfig-
>write_config($vmid, $conf);
3081 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3086 __PACKAGE__-
>register_method({
3087 name
=> 'get_snapshot_config',
3088 path
=> '{vmid}/snapshot/{snapname}/config',
3091 description
=> "Get snapshot configuration",
3093 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3096 additionalProperties
=> 0,
3098 node
=> get_standard_option
('pve-node'),
3099 vmid
=> get_standard_option
('pve-vmid'),
3100 snapname
=> get_standard_option
('pve-snapshot-name'),
3103 returns
=> { type
=> "object" },
3107 my $rpcenv = PVE
::RPCEnvironment
::get
();
3109 my $authuser = $rpcenv->get_user();
3111 my $vmid = extract_param
($param, 'vmid');
3113 my $snapname = extract_param
($param, 'snapname');
3115 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3117 my $snap = $conf->{snapshots
}->{$snapname};
3119 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3124 __PACKAGE__-
>register_method({
3126 path
=> '{vmid}/snapshot/{snapname}/rollback',
3130 description
=> "Rollback VM state to specified snapshot.",
3132 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3135 additionalProperties
=> 0,
3137 node
=> get_standard_option
('pve-node'),
3138 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3139 snapname
=> get_standard_option
('pve-snapshot-name'),
3144 description
=> "the task ID.",
3149 my $rpcenv = PVE
::RPCEnvironment
::get
();
3151 my $authuser = $rpcenv->get_user();
3153 my $node = extract_param
($param, 'node');
3155 my $vmid = extract_param
($param, 'vmid');
3157 my $snapname = extract_param
($param, 'snapname');
3160 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3161 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3164 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3167 __PACKAGE__-
>register_method({
3168 name
=> 'delsnapshot',
3169 path
=> '{vmid}/snapshot/{snapname}',
3173 description
=> "Delete a VM snapshot.",
3175 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3178 additionalProperties
=> 0,
3180 node
=> get_standard_option
('pve-node'),
3181 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3182 snapname
=> get_standard_option
('pve-snapshot-name'),
3186 description
=> "For removal from config file, even if removing disk snapshots fails.",
3192 description
=> "the task ID.",
3197 my $rpcenv = PVE
::RPCEnvironment
::get
();
3199 my $authuser = $rpcenv->get_user();
3201 my $node = extract_param
($param, 'node');
3203 my $vmid = extract_param
($param, 'vmid');
3205 my $snapname = extract_param
($param, 'snapname');
3208 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3209 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3212 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3215 __PACKAGE__-
>register_method({
3217 path
=> '{vmid}/template',
3221 description
=> "Create a Template.",
3223 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3224 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3227 additionalProperties
=> 0,
3229 node
=> get_standard_option
('pve-node'),
3230 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3234 description
=> "If you want to convert only 1 disk to base image.",
3235 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3240 returns
=> { type
=> 'null'},
3244 my $rpcenv = PVE
::RPCEnvironment
::get
();
3246 my $authuser = $rpcenv->get_user();
3248 my $node = extract_param
($param, 'node');
3250 my $vmid = extract_param
($param, 'vmid');
3252 my $disk = extract_param
($param, 'disk');
3254 my $updatefn = sub {
3256 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3258 PVE
::QemuConfig-
>check_lock($conf);
3260 die "unable to create template, because VM contains snapshots\n"
3261 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3263 die "you can't convert a template to a template\n"
3264 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3266 die "you can't convert a VM to template if VM is running\n"
3267 if PVE
::QemuServer
::check_running
($vmid);
3270 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3273 $conf->{template
} = 1;
3274 PVE
::QemuConfig-
>write_config($vmid, $conf);
3276 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3279 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);