1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::RPCEnvironment
;
20 use PVE
::AccessControl
;
24 use PVE
::API2
::Firewall
::VM
;
25 use PVE
::HA
::Env
::PVE2
;
28 use Data
::Dumper
; # fixme: remove
30 use base
qw(PVE::RESTHandler);
32 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
34 my $resolve_cdrom_alias = sub {
37 if (my $value = $param->{cdrom
}) {
38 $value .= ",media=cdrom" if $value !~ m/media=/;
39 $param->{ide2
} = $value;
40 delete $param->{cdrom
};
44 my $check_storage_access = sub {
45 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
47 PVE
::QemuServer
::foreach_drive
($settings, sub {
48 my ($ds, $drive) = @_;
50 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
52 my $volid = $drive->{file
};
54 if (!$volid || $volid eq 'none') {
56 } elsif ($isCDROM && ($volid eq 'cdrom')) {
57 $rpcenv->check($authuser, "/", ['Sys.Console']);
58 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
59 my ($storeid, $size) = ($2 || $default_storage, $3);
60 die "no storage ID specified (and no default storage)\n" if !$storeid;
61 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
63 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
68 my $check_storage_access_clone = sub {
69 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
73 PVE
::QemuServer
::foreach_drive
($conf, sub {
74 my ($ds, $drive) = @_;
76 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
78 my $volid = $drive->{file
};
80 return if !$volid || $volid eq 'none';
83 if ($volid eq 'cdrom') {
84 $rpcenv->check($authuser, "/", ['Sys.Console']);
86 # we simply allow access
87 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
88 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
89 $sharedvm = 0 if !$scfg->{shared
};
93 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
94 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
95 $sharedvm = 0 if !$scfg->{shared
};
97 $sid = $storage if $storage;
98 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
105 # Note: $pool is only needed when creating a VM, because pool permissions
106 # are automatically inherited if VM already exists inside a pool.
107 my $create_disks = sub {
108 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
113 PVE
::QemuServer
::foreach_drive
($settings, sub {
114 my ($ds, $disk) = @_;
116 my $volid = $disk->{file
};
118 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
119 delete $disk->{size
};
120 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
121 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
122 my ($storeid, $size) = ($2 || $default_storage, $3);
123 die "no storage ID specified (and no default storage)\n" if !$storeid;
124 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
125 my $fmt = $disk->{format
} || $defformat;
128 if ($ds eq 'efidisk0') {
130 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
131 die "uefi vars image not found\n" if ! -f
$ovmfvars;
132 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
134 $disk->{file
} = $volid;
135 $disk->{size
} = 128*1024;
136 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
137 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
138 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
139 my $path = PVE
::Storage
::path
($storecfg, $volid);
140 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
141 push @$efidiskcmd, $ovmfvars;
142 push @$efidiskcmd, $path;
144 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
146 eval { PVE
::Tools
::run_command
($efidiskcmd); };
148 die "Copying of EFI Vars image failed: $err" if $err;
150 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
151 $fmt, undef, $size*1024*1024);
152 $disk->{file
} = $volid;
153 $disk->{size
} = $size*1024*1024*1024;
155 push @$vollist, $volid;
156 delete $disk->{format
}; # no longer needed
157 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
160 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
162 my $volid_is_new = 1;
165 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
166 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
171 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
173 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
175 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
177 die "volume $volid does not exists\n" if !$size;
179 $disk->{size
} = $size;
182 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
186 # free allocated images on error
188 syslog
('err', "VM $vmid creating disks failed");
189 foreach my $volid (@$vollist) {
190 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
196 # modify vm config if everything went well
197 foreach my $ds (keys %$res) {
198 $conf->{$ds} = $res->{$ds};
215 my $memoryoptions = {
221 my $hwtypeoptions = {
233 my $generaloptions = {
240 'migrate_downtime' => 1,
241 'migrate_speed' => 1,
253 my $vmpoweroptions = {
262 my $check_vm_modify_config_perm = sub {
263 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
265 return 1 if $authuser eq 'root@pam';
267 foreach my $opt (@$key_list) {
268 # disk checks need to be done somewhere else
269 next if PVE
::QemuServer
::is_valid_drivename
($opt);
270 next if $opt eq 'cdrom';
271 next if $opt =~ m/^unused\d+$/;
273 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
274 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
275 } elsif ($memoryoptions->{$opt}) {
276 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
277 } elsif ($hwtypeoptions->{$opt}) {
278 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
279 } elsif ($generaloptions->{$opt}) {
280 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
281 # special case for startup since it changes host behaviour
282 if ($opt eq 'startup') {
283 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
285 } elsif ($vmpoweroptions->{$opt}) {
286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
287 } elsif ($diskoptions->{$opt}) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
289 } elsif ($opt =~ m/^net\d+$/) {
290 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
292 # catches usb\d+, hostpci\d+, args, lock, etc.
293 # new options will be checked here
294 die "only root can set '$opt' config\n";
301 __PACKAGE__-
>register_method({
305 description
=> "Virtual machine index (per node).",
307 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
311 protected
=> 1, # qemu pid files are only readable by root
313 additionalProperties
=> 0,
315 node
=> get_standard_option
('pve-node'),
319 description
=> "Determine the full status of active VMs.",
329 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
334 my $rpcenv = PVE
::RPCEnvironment
::get
();
335 my $authuser = $rpcenv->get_user();
337 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
340 foreach my $vmid (keys %$vmstatus) {
341 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
343 my $data = $vmstatus->{$vmid};
344 $data->{vmid
} = int($vmid);
353 __PACKAGE__-
>register_method({
357 description
=> "Create or restore a virtual machine.",
359 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
360 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
361 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
362 user
=> 'all', # check inside
367 additionalProperties
=> 0,
368 properties
=> PVE
::QemuServer
::json_config_properties
(
370 node
=> get_standard_option
('pve-node'),
371 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
373 description
=> "The backup file.",
377 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
379 storage
=> get_standard_option
('pve-storage-id', {
380 description
=> "Default storage.",
382 completion
=> \
&PVE
::QemuServer
::complete_storage
,
387 description
=> "Allow to overwrite existing VM.",
388 requires
=> 'archive',
393 description
=> "Assign a unique random ethernet address.",
394 requires
=> 'archive',
398 type
=> 'string', format
=> 'pve-poolid',
399 description
=> "Add the VM to the specified pool.",
409 my $rpcenv = PVE
::RPCEnvironment
::get
();
411 my $authuser = $rpcenv->get_user();
413 my $node = extract_param
($param, 'node');
415 my $vmid = extract_param
($param, 'vmid');
417 my $archive = extract_param
($param, 'archive');
419 my $storage = extract_param
($param, 'storage');
421 my $force = extract_param
($param, 'force');
423 my $unique = extract_param
($param, 'unique');
425 my $pool = extract_param
($param, 'pool');
427 my $filename = PVE
::QemuConfig-
>config_file($vmid);
429 my $storecfg = PVE
::Storage
::config
();
431 PVE
::Cluster
::check_cfs_quorum
();
433 if (defined($pool)) {
434 $rpcenv->check_pool_exist($pool);
437 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
438 if defined($storage);
440 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
442 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
444 } elsif ($archive && $force && (-f
$filename) &&
445 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
446 # OK: user has VM.Backup permissions, and want to restore an existing VM
452 &$resolve_cdrom_alias($param);
454 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
456 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
458 foreach my $opt (keys %$param) {
459 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
460 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
461 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
463 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
464 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
468 PVE
::QemuServer
::add_random_macs
($param);
470 my $keystr = join(' ', keys %$param);
471 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
473 if ($archive eq '-') {
474 die "pipe requires cli environment\n"
475 if $rpcenv->{type
} ne 'cli';
477 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
478 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
482 my $restorefn = sub {
483 my $vmlist = PVE
::Cluster
::get_vmlist
();
484 if ($vmlist->{ids
}->{$vmid}) {
485 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
486 if ($current_node eq $node) {
487 my $conf = PVE
::QemuConfig-
>load_config($vmid);
489 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
491 die "unable to restore vm $vmid - config file already exists\n"
494 die "unable to restore vm $vmid - vm is running\n"
495 if PVE
::QemuServer
::check_running
($vmid);
497 die "unable to restore vm $vmid - vm is a template\n"
498 if PVE
::QemuConfig-
>is_template($conf);
501 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
506 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
509 unique
=> $unique });
511 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
514 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
520 PVE
::Cluster
::check_vmid_unused
($vmid);
530 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
532 # try to be smart about bootdisk
533 my @disks = PVE
::QemuServer
::valid_drive_names
();
535 foreach my $ds (reverse @disks) {
536 next if !$conf->{$ds};
537 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
538 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
542 if (!$conf->{bootdisk
} && $firstdisk) {
543 $conf->{bootdisk
} = $firstdisk;
546 # auto generate uuid if user did not specify smbios1 option
547 if (!$conf->{smbios1
}) {
548 my ($uuid, $uuid_str);
549 UUID
::generate
($uuid);
550 UUID
::unparse
($uuid, $uuid_str);
551 $conf->{smbios1
} = "uuid=$uuid_str";
554 PVE
::QemuConfig-
>write_config($vmid, $conf);
560 foreach my $volid (@$vollist) {
561 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
564 die "create failed - $err";
567 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
570 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
573 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
576 __PACKAGE__-
>register_method({
581 description
=> "Directory index",
586 additionalProperties
=> 0,
588 node
=> get_standard_option
('pve-node'),
589 vmid
=> get_standard_option
('pve-vmid'),
597 subdir
=> { type
=> 'string' },
600 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
606 { subdir
=> 'config' },
607 { subdir
=> 'pending' },
608 { subdir
=> 'status' },
609 { subdir
=> 'unlink' },
610 { subdir
=> 'vncproxy' },
611 { subdir
=> 'migrate' },
612 { subdir
=> 'resize' },
613 { subdir
=> 'move' },
615 { subdir
=> 'rrddata' },
616 { subdir
=> 'monitor' },
617 { subdir
=> 'snapshot' },
618 { subdir
=> 'spiceproxy' },
619 { subdir
=> 'sendkey' },
620 { subdir
=> 'firewall' },
626 __PACKAGE__-
>register_method ({
627 subclass
=> "PVE::API2::Firewall::VM",
628 path
=> '{vmid}/firewall',
631 __PACKAGE__-
>register_method({
633 path
=> '{vmid}/rrd',
635 protected
=> 1, # fixme: can we avoid that?
637 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
639 description
=> "Read VM RRD statistics (returns PNG)",
641 additionalProperties
=> 0,
643 node
=> get_standard_option
('pve-node'),
644 vmid
=> get_standard_option
('pve-vmid'),
646 description
=> "Specify the time frame you are interested in.",
648 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
651 description
=> "The list of datasources you want to display.",
652 type
=> 'string', format
=> 'pve-configid-list',
655 description
=> "The RRD consolidation function",
657 enum
=> [ 'AVERAGE', 'MAX' ],
665 filename
=> { type
=> 'string' },
671 return PVE
::Cluster
::create_rrd_graph
(
672 "pve2-vm/$param->{vmid}", $param->{timeframe
},
673 $param->{ds
}, $param->{cf
});
677 __PACKAGE__-
>register_method({
679 path
=> '{vmid}/rrddata',
681 protected
=> 1, # fixme: can we avoid that?
683 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
685 description
=> "Read VM RRD statistics",
687 additionalProperties
=> 0,
689 node
=> get_standard_option
('pve-node'),
690 vmid
=> get_standard_option
('pve-vmid'),
692 description
=> "Specify the time frame you are interested in.",
694 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
697 description
=> "The RRD consolidation function",
699 enum
=> [ 'AVERAGE', 'MAX' ],
714 return PVE
::Cluster
::create_rrd_data
(
715 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
719 __PACKAGE__-
>register_method({
721 path
=> '{vmid}/config',
724 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
726 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
729 additionalProperties
=> 0,
731 node
=> get_standard_option
('pve-node'),
732 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
734 description
=> "Get current values (instead of pending values).",
746 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
753 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
755 delete $conf->{snapshots
};
757 if (!$param->{current
}) {
758 foreach my $opt (keys %{$conf->{pending
}}) {
759 next if $opt eq 'delete';
760 my $value = $conf->{pending
}->{$opt};
761 next if ref($value); # just to be sure
762 $conf->{$opt} = $value;
764 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
765 foreach my $opt (keys %$pending_delete_hash) {
766 delete $conf->{$opt} if $conf->{$opt};
770 delete $conf->{pending
};
775 __PACKAGE__-
>register_method({
776 name
=> 'vm_pending',
777 path
=> '{vmid}/pending',
780 description
=> "Get virtual machine configuration, including pending changes.",
782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
785 additionalProperties
=> 0,
787 node
=> get_standard_option
('pve-node'),
788 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
797 description
=> "Configuration option name.",
801 description
=> "Current value.",
806 description
=> "Pending value.",
811 description
=> "Indicates a pending delete request if present and not 0. " .
812 "The value 2 indicates a force-delete request.",
824 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
826 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
830 foreach my $opt (keys %$conf) {
831 next if ref($conf->{$opt});
832 my $item = { key
=> $opt };
833 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
834 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
835 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
839 foreach my $opt (keys %{$conf->{pending
}}) {
840 next if $opt eq 'delete';
841 next if ref($conf->{pending
}->{$opt}); # just to be sure
842 next if defined($conf->{$opt});
843 my $item = { key
=> $opt };
844 $item->{pending
} = $conf->{pending
}->{$opt};
848 while (my ($opt, $force) = each %$pending_delete_hash) {
849 next if $conf->{pending
}->{$opt}; # just to be sure
850 next if $conf->{$opt};
851 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
858 # POST/PUT {vmid}/config implementation
860 # The original API used PUT (idempotent) an we assumed that all operations
861 # are fast. But it turned out that almost any configuration change can
862 # involve hot-plug actions, or disk alloc/free. Such actions can take long
863 # time to complete and have side effects (not idempotent).
865 # The new implementation uses POST and forks a worker process. We added
866 # a new option 'background_delay'. If specified we wait up to
867 # 'background_delay' second for the worker task to complete. It returns null
868 # if the task is finished within that time, else we return the UPID.
870 my $update_vm_api = sub {
871 my ($param, $sync) = @_;
873 my $rpcenv = PVE
::RPCEnvironment
::get
();
875 my $authuser = $rpcenv->get_user();
877 my $node = extract_param
($param, 'node');
879 my $vmid = extract_param
($param, 'vmid');
881 my $digest = extract_param
($param, 'digest');
883 my $background_delay = extract_param
($param, 'background_delay');
885 my @paramarr = (); # used for log message
886 foreach my $key (keys %$param) {
887 push @paramarr, "-$key", $param->{$key};
890 my $skiplock = extract_param
($param, 'skiplock');
891 raise_param_exc
({ skiplock
=> "Only root may use this option." })
892 if $skiplock && $authuser ne 'root@pam';
894 my $delete_str = extract_param
($param, 'delete');
896 my $revert_str = extract_param
($param, 'revert');
898 my $force = extract_param
($param, 'force');
900 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
902 my $storecfg = PVE
::Storage
::config
();
904 my $defaults = PVE
::QemuServer
::load_defaults
();
906 &$resolve_cdrom_alias($param);
908 # now try to verify all parameters
911 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
912 if (!PVE
::QemuServer
::option_exists
($opt)) {
913 raise_param_exc
({ revert
=> "unknown option '$opt'" });
916 raise_param_exc
({ delete => "you can't use '-$opt' and " .
917 "-revert $opt' at the same time" })
918 if defined($param->{$opt});
924 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
925 $opt = 'ide2' if $opt eq 'cdrom';
927 raise_param_exc
({ delete => "you can't use '-$opt' and " .
928 "-delete $opt' at the same time" })
929 if defined($param->{$opt});
931 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
932 "-revert $opt' at the same time" })
935 if (!PVE
::QemuServer
::option_exists
($opt)) {
936 raise_param_exc
({ delete => "unknown option '$opt'" });
942 foreach my $opt (keys %$param) {
943 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
945 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
946 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
947 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
948 } elsif ($opt =~ m/^net(\d+)$/) {
950 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
951 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
955 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
957 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
959 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
963 my $conf = PVE
::QemuConfig-
>load_config($vmid);
965 die "checksum missmatch (file change by other user?)\n"
966 if $digest && $digest ne $conf->{digest
};
968 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
970 foreach my $opt (keys %$revert) {
971 if (defined($conf->{$opt})) {
972 $param->{$opt} = $conf->{$opt};
973 } elsif (defined($conf->{pending
}->{$opt})) {
978 if ($param->{memory
} || defined($param->{balloon
})) {
979 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
980 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
982 die "balloon value too large (must be smaller than assigned memory)\n"
983 if $balloon && $balloon > $maxmem;
986 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
990 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
992 # write updates to pending section
994 my $modified = {}; # record what $option we modify
996 foreach my $opt (@delete) {
997 $modified->{$opt} = 1;
998 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
999 if ($opt =~ m/^unused/) {
1000 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1001 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1002 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1003 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1004 delete $conf->{$opt};
1005 PVE
::QemuConfig-
>write_config($vmid, $conf);
1007 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1008 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1009 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1010 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1011 if defined($conf->{pending
}->{$opt});
1012 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1013 PVE
::QemuConfig-
>write_config($vmid, $conf);
1015 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1016 PVE
::QemuConfig-
>write_config($vmid, $conf);
1020 foreach my $opt (keys %$param) { # add/change
1021 $modified->{$opt} = 1;
1022 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1023 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1025 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1026 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1027 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1028 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1030 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1032 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1033 if defined($conf->{pending
}->{$opt});
1035 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1037 $conf->{pending
}->{$opt} = $param->{$opt};
1039 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1040 PVE
::QemuConfig-
>write_config($vmid, $conf);
1043 # remove pending changes when nothing changed
1044 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1045 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1046 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1048 return if !scalar(keys %{$conf->{pending
}});
1050 my $running = PVE
::QemuServer
::check_running
($vmid);
1052 # apply pending changes
1054 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1058 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1059 raise_param_exc
($errors) if scalar(keys %$errors);
1061 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1071 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1073 if ($background_delay) {
1075 # Note: It would be better to do that in the Event based HTTPServer
1076 # to avoid blocking call to sleep.
1078 my $end_time = time() + $background_delay;
1080 my $task = PVE
::Tools
::upid_decode
($upid);
1083 while (time() < $end_time) {
1084 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1086 sleep(1); # this gets interrupted when child process ends
1090 my $status = PVE
::Tools
::upid_read_status
($upid);
1091 return undef if $status eq 'OK';
1100 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1103 my $vm_config_perm_list = [
1108 'VM.Config.Network',
1110 'VM.Config.Options',
1113 __PACKAGE__-
>register_method({
1114 name
=> 'update_vm_async',
1115 path
=> '{vmid}/config',
1119 description
=> "Set virtual machine options (asynchrounous API).",
1121 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1124 additionalProperties
=> 0,
1125 properties
=> PVE
::QemuServer
::json_config_properties
(
1127 node
=> get_standard_option
('pve-node'),
1128 vmid
=> get_standard_option
('pve-vmid'),
1129 skiplock
=> get_standard_option
('skiplock'),
1131 type
=> 'string', format
=> 'pve-configid-list',
1132 description
=> "A list of settings you want to delete.",
1136 type
=> 'string', format
=> 'pve-configid-list',
1137 description
=> "Revert a pending change.",
1142 description
=> $opt_force_description,
1144 requires
=> 'delete',
1148 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1152 background_delay
=> {
1154 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1165 code
=> $update_vm_api,
1168 __PACKAGE__-
>register_method({
1169 name
=> 'update_vm',
1170 path
=> '{vmid}/config',
1174 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1176 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1179 additionalProperties
=> 0,
1180 properties
=> PVE
::QemuServer
::json_config_properties
(
1182 node
=> get_standard_option
('pve-node'),
1183 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1184 skiplock
=> get_standard_option
('skiplock'),
1186 type
=> 'string', format
=> 'pve-configid-list',
1187 description
=> "A list of settings you want to delete.",
1191 type
=> 'string', format
=> 'pve-configid-list',
1192 description
=> "Revert a pending change.",
1197 description
=> $opt_force_description,
1199 requires
=> 'delete',
1203 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1209 returns
=> { type
=> 'null' },
1212 &$update_vm_api($param, 1);
1218 __PACKAGE__-
>register_method({
1219 name
=> 'destroy_vm',
1224 description
=> "Destroy the vm (also delete all used/owned volumes).",
1226 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1229 additionalProperties
=> 0,
1231 node
=> get_standard_option
('pve-node'),
1232 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1233 skiplock
=> get_standard_option
('skiplock'),
1242 my $rpcenv = PVE
::RPCEnvironment
::get
();
1244 my $authuser = $rpcenv->get_user();
1246 my $vmid = $param->{vmid
};
1248 my $skiplock = $param->{skiplock
};
1249 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1250 if $skiplock && $authuser ne 'root@pam';
1253 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1255 my $storecfg = PVE
::Storage
::config
();
1257 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1259 die "unable to remove VM $vmid - used in HA resources\n"
1260 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1262 # early tests (repeat after locking)
1263 die "VM $vmid is running - destroy failed\n"
1264 if PVE
::QemuServer
::check_running
($vmid);
1269 syslog
('info', "destroy VM $vmid: $upid\n");
1271 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1273 PVE
::AccessControl
::remove_vm_access
($vmid);
1275 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1278 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1281 __PACKAGE__-
>register_method({
1283 path
=> '{vmid}/unlink',
1287 description
=> "Unlink/delete disk images.",
1289 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1292 additionalProperties
=> 0,
1294 node
=> get_standard_option
('pve-node'),
1295 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1297 type
=> 'string', format
=> 'pve-configid-list',
1298 description
=> "A list of disk IDs you want to delete.",
1302 description
=> $opt_force_description,
1307 returns
=> { type
=> 'null'},
1311 $param->{delete} = extract_param
($param, 'idlist');
1313 __PACKAGE__-
>update_vm($param);
1320 __PACKAGE__-
>register_method({
1322 path
=> '{vmid}/vncproxy',
1326 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1328 description
=> "Creates a TCP VNC proxy connections.",
1330 additionalProperties
=> 0,
1332 node
=> get_standard_option
('pve-node'),
1333 vmid
=> get_standard_option
('pve-vmid'),
1337 description
=> "starts websockify instead of vncproxy",
1342 additionalProperties
=> 0,
1344 user
=> { type
=> 'string' },
1345 ticket
=> { type
=> 'string' },
1346 cert
=> { type
=> 'string' },
1347 port
=> { type
=> 'integer' },
1348 upid
=> { type
=> 'string' },
1354 my $rpcenv = PVE
::RPCEnvironment
::get
();
1356 my $authuser = $rpcenv->get_user();
1358 my $vmid = $param->{vmid
};
1359 my $node = $param->{node
};
1360 my $websocket = $param->{websocket
};
1362 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1364 my $authpath = "/vms/$vmid";
1366 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1368 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1371 my ($remip, $family);
1374 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1375 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1376 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1377 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1379 $family = PVE
::Tools
::get_host_address_family
($node);
1382 my $port = PVE
::Tools
::next_vnc_port
($family);
1389 syslog
('info', "starting vnc proxy $upid\n");
1393 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1395 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1397 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1398 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1399 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1400 '-timeout', $timeout, '-authpath', $authpath,
1401 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1404 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1406 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1408 my $qmstr = join(' ', @$qmcmd);
1410 # also redirect stderr (else we get RFB protocol errors)
1411 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1414 PVE
::Tools
::run_command
($cmd);
1419 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1421 PVE
::Tools
::wait_for_vnc_port
($port);
1432 __PACKAGE__-
>register_method({
1433 name
=> 'vncwebsocket',
1434 path
=> '{vmid}/vncwebsocket',
1437 description
=> "You also need to pass a valid ticket (vncticket).",
1438 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1440 description
=> "Opens a weksocket for VNC traffic.",
1442 additionalProperties
=> 0,
1444 node
=> get_standard_option
('pve-node'),
1445 vmid
=> get_standard_option
('pve-vmid'),
1447 description
=> "Ticket from previous call to vncproxy.",
1452 description
=> "Port number returned by previous vncproxy call.",
1462 port
=> { type
=> 'string' },
1468 my $rpcenv = PVE
::RPCEnvironment
::get
();
1470 my $authuser = $rpcenv->get_user();
1472 my $vmid = $param->{vmid
};
1473 my $node = $param->{node
};
1475 my $authpath = "/vms/$vmid";
1477 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1479 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1481 # Note: VNC ports are acessible from outside, so we do not gain any
1482 # security if we verify that $param->{port} belongs to VM $vmid. This
1483 # check is done by verifying the VNC ticket (inside VNC protocol).
1485 my $port = $param->{port
};
1487 return { port
=> $port };
1490 __PACKAGE__-
>register_method({
1491 name
=> 'spiceproxy',
1492 path
=> '{vmid}/spiceproxy',
1497 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1499 description
=> "Returns a SPICE configuration to connect to the VM.",
1501 additionalProperties
=> 0,
1503 node
=> get_standard_option
('pve-node'),
1504 vmid
=> get_standard_option
('pve-vmid'),
1505 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1508 returns
=> get_standard_option
('remote-viewer-config'),
1512 my $rpcenv = PVE
::RPCEnvironment
::get
();
1514 my $authuser = $rpcenv->get_user();
1516 my $vmid = $param->{vmid
};
1517 my $node = $param->{node
};
1518 my $proxy = $param->{proxy
};
1520 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1521 my $title = "VM $vmid";
1522 $title .= " - ". $conf->{name
} if $conf->{name
};
1524 my $port = PVE
::QemuServer
::spice_port
($vmid);
1526 my ($ticket, undef, $remote_viewer_config) =
1527 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1529 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1530 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1532 return $remote_viewer_config;
1535 __PACKAGE__-
>register_method({
1537 path
=> '{vmid}/status',
1540 description
=> "Directory index",
1545 additionalProperties
=> 0,
1547 node
=> get_standard_option
('pve-node'),
1548 vmid
=> get_standard_option
('pve-vmid'),
1556 subdir
=> { type
=> 'string' },
1559 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1565 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1568 { subdir
=> 'current' },
1569 { subdir
=> 'start' },
1570 { subdir
=> 'stop' },
1576 __PACKAGE__-
>register_method({
1577 name
=> 'vm_status',
1578 path
=> '{vmid}/status/current',
1581 protected
=> 1, # qemu pid files are only readable by root
1582 description
=> "Get virtual machine status.",
1584 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1587 additionalProperties
=> 0,
1589 node
=> get_standard_option
('pve-node'),
1590 vmid
=> get_standard_option
('pve-vmid'),
1593 returns
=> { type
=> 'object' },
1598 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1600 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1601 my $status = $vmstatus->{$param->{vmid
}};
1603 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1605 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1610 __PACKAGE__-
>register_method({
1612 path
=> '{vmid}/status/start',
1616 description
=> "Start virtual machine.",
1618 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1621 additionalProperties
=> 0,
1623 node
=> get_standard_option
('pve-node'),
1624 vmid
=> get_standard_option
('pve-vmid',
1625 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1626 skiplock
=> get_standard_option
('skiplock'),
1627 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1628 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1629 machine
=> get_standard_option
('pve-qm-machine'),
1638 my $rpcenv = PVE
::RPCEnvironment
::get
();
1640 my $authuser = $rpcenv->get_user();
1642 my $node = extract_param
($param, 'node');
1644 my $vmid = extract_param
($param, 'vmid');
1646 my $machine = extract_param
($param, 'machine');
1648 my $stateuri = extract_param
($param, 'stateuri');
1649 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1650 if $stateuri && $authuser ne 'root@pam';
1652 my $skiplock = extract_param
($param, 'skiplock');
1653 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1654 if $skiplock && $authuser ne 'root@pam';
1656 my $migratedfrom = extract_param
($param, 'migratedfrom');
1657 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1658 if $migratedfrom && $authuser ne 'root@pam';
1660 # read spice ticket from STDIN
1662 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1663 if (defined(my $line = <>)) {
1665 $spice_ticket = $line;
1669 PVE
::Cluster
::check_cfs_quorum
();
1671 my $storecfg = PVE
::Storage
::config
();
1673 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1674 $rpcenv->{type
} ne 'ha') {
1679 my $service = "vm:$vmid";
1681 my $cmd = ['ha-manager', 'enable', $service];
1683 print "Executing HA start for VM $vmid\n";
1685 PVE
::Tools
::run_command
($cmd);
1690 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1697 syslog
('info', "start VM $vmid: $upid\n");
1699 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1700 $machine, $spice_ticket);
1705 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1709 __PACKAGE__-
>register_method({
1711 path
=> '{vmid}/status/stop',
1715 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1716 "is akin to pulling the power plug of a running computer and may damage the VM data",
1718 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1721 additionalProperties
=> 0,
1723 node
=> get_standard_option
('pve-node'),
1724 vmid
=> get_standard_option
('pve-vmid',
1725 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1726 skiplock
=> get_standard_option
('skiplock'),
1727 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1729 description
=> "Wait maximal timeout seconds.",
1735 description
=> "Do not deactivate storage volumes.",
1748 my $rpcenv = PVE
::RPCEnvironment
::get
();
1750 my $authuser = $rpcenv->get_user();
1752 my $node = extract_param
($param, 'node');
1754 my $vmid = extract_param
($param, 'vmid');
1756 my $skiplock = extract_param
($param, 'skiplock');
1757 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1758 if $skiplock && $authuser ne 'root@pam';
1760 my $keepActive = extract_param
($param, 'keepActive');
1761 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1762 if $keepActive && $authuser ne 'root@pam';
1764 my $migratedfrom = extract_param
($param, 'migratedfrom');
1765 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1766 if $migratedfrom && $authuser ne 'root@pam';
1769 my $storecfg = PVE
::Storage
::config
();
1771 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1776 my $service = "vm:$vmid";
1778 my $cmd = ['ha-manager', 'disable', $service];
1780 print "Executing HA stop for VM $vmid\n";
1782 PVE
::Tools
::run_command
($cmd);
1787 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1793 syslog
('info', "stop VM $vmid: $upid\n");
1795 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1796 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1801 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1805 __PACKAGE__-
>register_method({
1807 path
=> '{vmid}/status/reset',
1811 description
=> "Reset virtual machine.",
1813 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1816 additionalProperties
=> 0,
1818 node
=> get_standard_option
('pve-node'),
1819 vmid
=> get_standard_option
('pve-vmid',
1820 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1821 skiplock
=> get_standard_option
('skiplock'),
1830 my $rpcenv = PVE
::RPCEnvironment
::get
();
1832 my $authuser = $rpcenv->get_user();
1834 my $node = extract_param
($param, 'node');
1836 my $vmid = extract_param
($param, 'vmid');
1838 my $skiplock = extract_param
($param, 'skiplock');
1839 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1840 if $skiplock && $authuser ne 'root@pam';
1842 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1847 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1852 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1855 __PACKAGE__-
>register_method({
1856 name
=> 'vm_shutdown',
1857 path
=> '{vmid}/status/shutdown',
1861 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1862 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1864 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1867 additionalProperties
=> 0,
1869 node
=> get_standard_option
('pve-node'),
1870 vmid
=> get_standard_option
('pve-vmid',
1871 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1872 skiplock
=> get_standard_option
('skiplock'),
1874 description
=> "Wait maximal timeout seconds.",
1880 description
=> "Make sure the VM stops.",
1886 description
=> "Do not deactivate storage volumes.",
1899 my $rpcenv = PVE
::RPCEnvironment
::get
();
1901 my $authuser = $rpcenv->get_user();
1903 my $node = extract_param
($param, 'node');
1905 my $vmid = extract_param
($param, 'vmid');
1907 my $skiplock = extract_param
($param, 'skiplock');
1908 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1909 if $skiplock && $authuser ne 'root@pam';
1911 my $keepActive = extract_param
($param, 'keepActive');
1912 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1913 if $keepActive && $authuser ne 'root@pam';
1915 my $storecfg = PVE
::Storage
::config
();
1919 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1920 # otherwise, we will infer a shutdown command, but run into the timeout,
1921 # then when the vm is resumed, it will instantly shutdown
1923 # checking the qmp status here to get feedback to the gui/cli/api
1924 # and the status query should not take too long
1927 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1931 if (!$err && $qmpstatus->{status
} eq "paused") {
1932 if ($param->{forceStop
}) {
1933 warn "VM is paused - stop instead of shutdown\n";
1936 die "VM is paused - cannot shutdown\n";
1943 syslog
('info', "shutdown VM $vmid: $upid\n");
1945 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1946 $shutdown, $param->{forceStop
}, $keepActive);
1951 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1954 __PACKAGE__-
>register_method({
1955 name
=> 'vm_suspend',
1956 path
=> '{vmid}/status/suspend',
1960 description
=> "Suspend virtual machine.",
1962 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1965 additionalProperties
=> 0,
1967 node
=> get_standard_option
('pve-node'),
1968 vmid
=> get_standard_option
('pve-vmid',
1969 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1970 skiplock
=> get_standard_option
('skiplock'),
1979 my $rpcenv = PVE
::RPCEnvironment
::get
();
1981 my $authuser = $rpcenv->get_user();
1983 my $node = extract_param
($param, 'node');
1985 my $vmid = extract_param
($param, 'vmid');
1987 my $skiplock = extract_param
($param, 'skiplock');
1988 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1989 if $skiplock && $authuser ne 'root@pam';
1991 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1996 syslog
('info', "suspend VM $vmid: $upid\n");
1998 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2003 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2006 __PACKAGE__-
>register_method({
2007 name
=> 'vm_resume',
2008 path
=> '{vmid}/status/resume',
2012 description
=> "Resume virtual machine.",
2014 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2017 additionalProperties
=> 0,
2019 node
=> get_standard_option
('pve-node'),
2020 vmid
=> get_standard_option
('pve-vmid',
2021 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2022 skiplock
=> get_standard_option
('skiplock'),
2023 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2033 my $rpcenv = PVE
::RPCEnvironment
::get
();
2035 my $authuser = $rpcenv->get_user();
2037 my $node = extract_param
($param, 'node');
2039 my $vmid = extract_param
($param, 'vmid');
2041 my $skiplock = extract_param
($param, 'skiplock');
2042 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2043 if $skiplock && $authuser ne 'root@pam';
2045 my $nocheck = extract_param
($param, 'nocheck');
2047 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2052 syslog
('info', "resume VM $vmid: $upid\n");
2054 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2059 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2062 __PACKAGE__-
>register_method({
2063 name
=> 'vm_sendkey',
2064 path
=> '{vmid}/sendkey',
2068 description
=> "Send key event to virtual machine.",
2070 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2073 additionalProperties
=> 0,
2075 node
=> get_standard_option
('pve-node'),
2076 vmid
=> get_standard_option
('pve-vmid',
2077 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2078 skiplock
=> get_standard_option
('skiplock'),
2080 description
=> "The key (qemu monitor encoding).",
2085 returns
=> { type
=> 'null'},
2089 my $rpcenv = PVE
::RPCEnvironment
::get
();
2091 my $authuser = $rpcenv->get_user();
2093 my $node = extract_param
($param, 'node');
2095 my $vmid = extract_param
($param, 'vmid');
2097 my $skiplock = extract_param
($param, 'skiplock');
2098 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2099 if $skiplock && $authuser ne 'root@pam';
2101 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2106 __PACKAGE__-
>register_method({
2107 name
=> 'vm_feature',
2108 path
=> '{vmid}/feature',
2112 description
=> "Check if feature for virtual machine is available.",
2114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2117 additionalProperties
=> 0,
2119 node
=> get_standard_option
('pve-node'),
2120 vmid
=> get_standard_option
('pve-vmid'),
2122 description
=> "Feature to check.",
2124 enum
=> [ 'snapshot', 'clone', 'copy' ],
2126 snapname
=> get_standard_option
('pve-snapshot-name', {
2134 hasFeature
=> { type
=> 'boolean' },
2137 items
=> { type
=> 'string' },
2144 my $node = extract_param
($param, 'node');
2146 my $vmid = extract_param
($param, 'vmid');
2148 my $snapname = extract_param
($param, 'snapname');
2150 my $feature = extract_param
($param, 'feature');
2152 my $running = PVE
::QemuServer
::check_running
($vmid);
2154 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2157 my $snap = $conf->{snapshots
}->{$snapname};
2158 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2161 my $storecfg = PVE
::Storage
::config
();
2163 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2164 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2167 hasFeature
=> $hasFeature,
2168 nodes
=> [ keys %$nodelist ],
2172 __PACKAGE__-
>register_method({
2174 path
=> '{vmid}/clone',
2178 description
=> "Create a copy of virtual machine/template.",
2180 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2181 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2182 "'Datastore.AllocateSpace' on any used storage.",
2185 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2187 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2188 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2193 additionalProperties
=> 0,
2195 node
=> get_standard_option
('pve-node'),
2196 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2197 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2200 type
=> 'string', format
=> 'dns-name',
2201 description
=> "Set a name for the new VM.",
2206 description
=> "Description for the new VM.",
2210 type
=> 'string', format
=> 'pve-poolid',
2211 description
=> "Add the new VM to the specified pool.",
2213 snapname
=> get_standard_option
('pve-snapshot-name', {
2216 storage
=> get_standard_option
('pve-storage-id', {
2217 description
=> "Target storage for full clone.",
2222 description
=> "Target format for file storage.",
2226 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2231 description
=> "Create a full copy of all disk. This is always done when " .
2232 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2235 target
=> get_standard_option
('pve-node', {
2236 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2247 my $rpcenv = PVE
::RPCEnvironment
::get
();
2249 my $authuser = $rpcenv->get_user();
2251 my $node = extract_param
($param, 'node');
2253 my $vmid = extract_param
($param, 'vmid');
2255 my $newid = extract_param
($param, 'newid');
2257 my $pool = extract_param
($param, 'pool');
2259 if (defined($pool)) {
2260 $rpcenv->check_pool_exist($pool);
2263 my $snapname = extract_param
($param, 'snapname');
2265 my $storage = extract_param
($param, 'storage');
2267 my $format = extract_param
($param, 'format');
2269 my $target = extract_param
($param, 'target');
2271 my $localnode = PVE
::INotify
::nodename
();
2273 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2275 PVE
::Cluster
::check_node_exists
($target) if $target;
2277 my $storecfg = PVE
::Storage
::config
();
2280 # check if storage is enabled on local node
2281 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2283 # check if storage is available on target node
2284 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2285 # clone only works if target storage is shared
2286 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2287 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2291 PVE
::Cluster
::check_cfs_quorum
();
2293 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2295 # exclusive lock if VM is running - else shared lock is enough;
2296 my $shared_lock = $running ?
0 : 1;
2300 # do all tests after lock
2301 # we also try to do all tests before we fork the worker
2303 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2305 PVE
::QemuConfig-
>check_lock($conf);
2307 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2309 die "unexpected state change\n" if $verify_running != $running;
2311 die "snapshot '$snapname' does not exist\n"
2312 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2314 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2316 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2318 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2320 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2322 die "unable to create VM $newid: config file already exists\n"
2325 my $newconf = { lock => 'clone' };
2330 foreach my $opt (keys %$oldconf) {
2331 my $value = $oldconf->{$opt};
2333 # do not copy snapshot related info
2334 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2335 $opt eq 'vmstate' || $opt eq 'snapstate';
2337 # no need to copy unused images, because VMID(owner) changes anyways
2338 next if $opt =~ m/^unused\d+$/;
2340 # always change MAC! address
2341 if ($opt =~ m/^net(\d+)$/) {
2342 my $net = PVE
::QemuServer
::parse_net
($value);
2343 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2344 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2345 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2346 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2347 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2348 die "unable to parse drive options for '$opt'\n" if !$drive;
2349 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2350 $newconf->{$opt} = $value; # simply copy configuration
2352 if ($param->{full
}) {
2353 die "Full clone feature is not available"
2354 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2355 $fullclone->{$opt} = 1;
2357 # not full means clone instead of copy
2358 die "Linked clone feature is not available"
2359 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2361 $drives->{$opt} = $drive;
2362 push @$vollist, $drive->{file
};
2365 # copy everything else
2366 $newconf->{$opt} = $value;
2370 # auto generate a new uuid
2371 my ($uuid, $uuid_str);
2372 UUID
::generate
($uuid);
2373 UUID
::unparse
($uuid, $uuid_str);
2374 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2375 $smbios1->{uuid
} = $uuid_str;
2376 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2378 delete $newconf->{template
};
2380 if ($param->{name
}) {
2381 $newconf->{name
} = $param->{name
};
2383 if ($oldconf->{name
}) {
2384 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2386 $newconf->{name
} = "Copy-of-VM-$vmid";
2390 if ($param->{description
}) {
2391 $newconf->{description
} = $param->{description
};
2394 # create empty/temp config - this fails if VM already exists on other node
2395 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2400 my $newvollist = [];
2403 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2405 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2407 foreach my $opt (keys %$drives) {
2408 my $drive = $drives->{$opt};
2410 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2411 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2413 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2415 PVE
::QemuConfig-
>write_config($newid, $newconf);
2418 delete $newconf->{lock};
2419 PVE
::QemuConfig-
>write_config($newid, $newconf);
2422 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2423 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2424 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2426 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2427 die "Failed to move config to node '$target' - rename failed: $!\n"
2428 if !rename($conffile, $newconffile);
2431 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2436 sleep 1; # some storage like rbd need to wait before release volume - really?
2438 foreach my $volid (@$newvollist) {
2439 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2442 die "clone failed: $err";
2448 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2450 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2453 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2454 # Aquire exclusive lock lock for $newid
2455 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2460 __PACKAGE__-
>register_method({
2461 name
=> 'move_vm_disk',
2462 path
=> '{vmid}/move_disk',
2466 description
=> "Move volume to different storage.",
2468 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2469 "and 'Datastore.AllocateSpace' permissions on the storage.",
2472 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2473 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2477 additionalProperties
=> 0,
2479 node
=> get_standard_option
('pve-node'),
2480 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2483 description
=> "The disk you want to move.",
2484 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2486 storage
=> get_standard_option
('pve-storage-id', {
2487 description
=> "Target storage.",
2488 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2492 description
=> "Target Format.",
2493 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2498 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2504 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2512 description
=> "the task ID.",
2517 my $rpcenv = PVE
::RPCEnvironment
::get
();
2519 my $authuser = $rpcenv->get_user();
2521 my $node = extract_param
($param, 'node');
2523 my $vmid = extract_param
($param, 'vmid');
2525 my $digest = extract_param
($param, 'digest');
2527 my $disk = extract_param
($param, 'disk');
2529 my $storeid = extract_param
($param, 'storage');
2531 my $format = extract_param
($param, 'format');
2533 my $storecfg = PVE
::Storage
::config
();
2535 my $updatefn = sub {
2537 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2539 PVE
::QemuConfig-
>check_lock($conf);
2541 die "checksum missmatch (file change by other user?)\n"
2542 if $digest && $digest ne $conf->{digest
};
2544 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2546 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2548 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2550 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2553 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2554 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2558 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2559 (!$format || !$oldfmt || $oldfmt eq $format);
2561 # this only checks snapshots because $disk is passed!
2562 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2563 die "you can't move a disk with snapshots and delete the source\n"
2564 if $snapshotted && $param->{delete};
2566 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2568 my $running = PVE
::QemuServer
::check_running
($vmid);
2570 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2574 my $newvollist = [];
2577 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2579 warn "moving disk with snapshots, snapshots will not be moved!\n"
2582 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2583 $vmid, $storeid, $format, 1, $newvollist);
2585 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2587 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2589 PVE
::QemuConfig-
>write_config($vmid, $conf);
2592 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2593 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2600 foreach my $volid (@$newvollist) {
2601 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2604 die "storage migration failed: $err";
2607 if ($param->{delete}) {
2609 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2610 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2616 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2619 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2622 __PACKAGE__-
>register_method({
2623 name
=> 'migrate_vm',
2624 path
=> '{vmid}/migrate',
2628 description
=> "Migrate virtual machine. Creates a new migration task.",
2630 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2633 additionalProperties
=> 0,
2635 node
=> get_standard_option
('pve-node'),
2636 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2637 target
=> get_standard_option
('pve-node', {
2638 description
=> "Target node.",
2639 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2643 description
=> "Use online/live migration.",
2648 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2655 description
=> "the task ID.",
2660 my $rpcenv = PVE
::RPCEnvironment
::get
();
2662 my $authuser = $rpcenv->get_user();
2664 my $target = extract_param
($param, 'target');
2666 my $localnode = PVE
::INotify
::nodename
();
2667 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2669 PVE
::Cluster
::check_cfs_quorum
();
2671 PVE
::Cluster
::check_node_exists
($target);
2673 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2675 my $vmid = extract_param
($param, 'vmid');
2677 raise_param_exc
({ force
=> "Only root may use this option." })
2678 if $param->{force
} && $authuser ne 'root@pam';
2681 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2683 # try to detect errors early
2685 PVE
::QemuConfig-
>check_lock($conf);
2687 if (PVE
::QemuServer
::check_running
($vmid)) {
2688 die "cant migrate running VM without --online\n"
2689 if !$param->{online
};
2692 my $storecfg = PVE
::Storage
::config
();
2693 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2695 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2700 my $service = "vm:$vmid";
2702 my $cmd = ['ha-manager', 'migrate', $service, $target];
2704 print "Executing HA migrate for VM $vmid to node $target\n";
2706 PVE
::Tools
::run_command
($cmd);
2711 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2718 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2721 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2726 __PACKAGE__-
>register_method({
2728 path
=> '{vmid}/monitor',
2732 description
=> "Execute Qemu monitor commands.",
2734 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2737 additionalProperties
=> 0,
2739 node
=> get_standard_option
('pve-node'),
2740 vmid
=> get_standard_option
('pve-vmid'),
2743 description
=> "The monitor command.",
2747 returns
=> { type
=> 'string'},
2751 my $vmid = $param->{vmid
};
2753 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2757 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2759 $res = "ERROR: $@" if $@;
2764 __PACKAGE__-
>register_method({
2765 name
=> 'resize_vm',
2766 path
=> '{vmid}/resize',
2770 description
=> "Extend volume size.",
2772 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2775 additionalProperties
=> 0,
2777 node
=> get_standard_option
('pve-node'),
2778 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2779 skiplock
=> get_standard_option
('skiplock'),
2782 description
=> "The disk you want to resize.",
2783 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2787 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2788 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.",
2792 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2798 returns
=> { type
=> 'null'},
2802 my $rpcenv = PVE
::RPCEnvironment
::get
();
2804 my $authuser = $rpcenv->get_user();
2806 my $node = extract_param
($param, 'node');
2808 my $vmid = extract_param
($param, 'vmid');
2810 my $digest = extract_param
($param, 'digest');
2812 my $disk = extract_param
($param, 'disk');
2814 my $sizestr = extract_param
($param, 'size');
2816 my $skiplock = extract_param
($param, 'skiplock');
2817 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2818 if $skiplock && $authuser ne 'root@pam';
2820 my $storecfg = PVE
::Storage
::config
();
2822 my $updatefn = sub {
2824 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2826 die "checksum missmatch (file change by other user?)\n"
2827 if $digest && $digest ne $conf->{digest
};
2828 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2830 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2832 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2834 my (undef, undef, undef, undef, undef, undef, $format) =
2835 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2837 die "can't resize volume: $disk if snapshot exists\n"
2838 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2840 my $volid = $drive->{file
};
2842 die "disk '$disk' has no associated volume\n" if !$volid;
2844 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2846 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2848 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2850 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2851 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2853 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2854 my ($ext, $newsize, $unit) = ($1, $2, $4);
2857 $newsize = $newsize * 1024;
2858 } elsif ($unit eq 'M') {
2859 $newsize = $newsize * 1024 * 1024;
2860 } elsif ($unit eq 'G') {
2861 $newsize = $newsize * 1024 * 1024 * 1024;
2862 } elsif ($unit eq 'T') {
2863 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2866 $newsize += $size if $ext;
2867 $newsize = int($newsize);
2869 die "unable to skrink disk size\n" if $newsize < $size;
2871 return if $size == $newsize;
2873 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2875 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2877 $drive->{size
} = $newsize;
2878 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2880 PVE
::QemuConfig-
>write_config($vmid, $conf);
2883 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2887 __PACKAGE__-
>register_method({
2888 name
=> 'snapshot_list',
2889 path
=> '{vmid}/snapshot',
2891 description
=> "List all snapshots.",
2893 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2896 protected
=> 1, # qemu pid files are only readable by root
2898 additionalProperties
=> 0,
2900 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2901 node
=> get_standard_option
('pve-node'),
2910 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2915 my $vmid = $param->{vmid
};
2917 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2918 my $snaphash = $conf->{snapshots
} || {};
2922 foreach my $name (keys %$snaphash) {
2923 my $d = $snaphash->{$name};
2926 snaptime
=> $d->{snaptime
} || 0,
2927 vmstate
=> $d->{vmstate
} ?
1 : 0,
2928 description
=> $d->{description
} || '',
2930 $item->{parent
} = $d->{parent
} if $d->{parent
};
2931 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2935 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2936 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2937 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2939 push @$res, $current;
2944 __PACKAGE__-
>register_method({
2946 path
=> '{vmid}/snapshot',
2950 description
=> "Snapshot a VM.",
2952 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2955 additionalProperties
=> 0,
2957 node
=> get_standard_option
('pve-node'),
2958 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2959 snapname
=> get_standard_option
('pve-snapshot-name'),
2963 description
=> "Save the vmstate",
2968 description
=> "A textual description or comment.",
2974 description
=> "the task ID.",
2979 my $rpcenv = PVE
::RPCEnvironment
::get
();
2981 my $authuser = $rpcenv->get_user();
2983 my $node = extract_param
($param, 'node');
2985 my $vmid = extract_param
($param, 'vmid');
2987 my $snapname = extract_param
($param, 'snapname');
2989 die "unable to use snapshot name 'current' (reserved name)\n"
2990 if $snapname eq 'current';
2993 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2994 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
2995 $param->{description
});
2998 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3001 __PACKAGE__-
>register_method({
3002 name
=> 'snapshot_cmd_idx',
3003 path
=> '{vmid}/snapshot/{snapname}',
3010 additionalProperties
=> 0,
3012 vmid
=> get_standard_option
('pve-vmid'),
3013 node
=> get_standard_option
('pve-node'),
3014 snapname
=> get_standard_option
('pve-snapshot-name'),
3023 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3030 push @$res, { cmd
=> 'rollback' };
3031 push @$res, { cmd
=> 'config' };
3036 __PACKAGE__-
>register_method({
3037 name
=> 'update_snapshot_config',
3038 path
=> '{vmid}/snapshot/{snapname}/config',
3042 description
=> "Update snapshot metadata.",
3044 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3047 additionalProperties
=> 0,
3049 node
=> get_standard_option
('pve-node'),
3050 vmid
=> get_standard_option
('pve-vmid'),
3051 snapname
=> get_standard_option
('pve-snapshot-name'),
3055 description
=> "A textual description or comment.",
3059 returns
=> { type
=> 'null' },
3063 my $rpcenv = PVE
::RPCEnvironment
::get
();
3065 my $authuser = $rpcenv->get_user();
3067 my $vmid = extract_param
($param, 'vmid');
3069 my $snapname = extract_param
($param, 'snapname');
3071 return undef if !defined($param->{description
});
3073 my $updatefn = sub {
3075 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3077 PVE
::QemuConfig-
>check_lock($conf);
3079 my $snap = $conf->{snapshots
}->{$snapname};
3081 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3083 $snap->{description
} = $param->{description
} if defined($param->{description
});
3085 PVE
::QemuConfig-
>write_config($vmid, $conf);
3088 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3093 __PACKAGE__-
>register_method({
3094 name
=> 'get_snapshot_config',
3095 path
=> '{vmid}/snapshot/{snapname}/config',
3098 description
=> "Get snapshot configuration",
3100 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3103 additionalProperties
=> 0,
3105 node
=> get_standard_option
('pve-node'),
3106 vmid
=> get_standard_option
('pve-vmid'),
3107 snapname
=> get_standard_option
('pve-snapshot-name'),
3110 returns
=> { type
=> "object" },
3114 my $rpcenv = PVE
::RPCEnvironment
::get
();
3116 my $authuser = $rpcenv->get_user();
3118 my $vmid = extract_param
($param, 'vmid');
3120 my $snapname = extract_param
($param, 'snapname');
3122 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3124 my $snap = $conf->{snapshots
}->{$snapname};
3126 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3131 __PACKAGE__-
>register_method({
3133 path
=> '{vmid}/snapshot/{snapname}/rollback',
3137 description
=> "Rollback VM state to specified snapshot.",
3139 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3142 additionalProperties
=> 0,
3144 node
=> get_standard_option
('pve-node'),
3145 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3146 snapname
=> get_standard_option
('pve-snapshot-name'),
3151 description
=> "the task ID.",
3156 my $rpcenv = PVE
::RPCEnvironment
::get
();
3158 my $authuser = $rpcenv->get_user();
3160 my $node = extract_param
($param, 'node');
3162 my $vmid = extract_param
($param, 'vmid');
3164 my $snapname = extract_param
($param, 'snapname');
3167 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3168 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3171 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3174 __PACKAGE__-
>register_method({
3175 name
=> 'delsnapshot',
3176 path
=> '{vmid}/snapshot/{snapname}',
3180 description
=> "Delete a VM snapshot.",
3182 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3185 additionalProperties
=> 0,
3187 node
=> get_standard_option
('pve-node'),
3188 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3189 snapname
=> get_standard_option
('pve-snapshot-name'),
3193 description
=> "For removal from config file, even if removing disk snapshots fails.",
3199 description
=> "the task ID.",
3204 my $rpcenv = PVE
::RPCEnvironment
::get
();
3206 my $authuser = $rpcenv->get_user();
3208 my $node = extract_param
($param, 'node');
3210 my $vmid = extract_param
($param, 'vmid');
3212 my $snapname = extract_param
($param, 'snapname');
3215 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3216 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3219 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3222 __PACKAGE__-
>register_method({
3224 path
=> '{vmid}/template',
3228 description
=> "Create a Template.",
3230 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3231 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3234 additionalProperties
=> 0,
3236 node
=> get_standard_option
('pve-node'),
3237 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3241 description
=> "If you want to convert only 1 disk to base image.",
3242 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3247 returns
=> { type
=> 'null'},
3251 my $rpcenv = PVE
::RPCEnvironment
::get
();
3253 my $authuser = $rpcenv->get_user();
3255 my $node = extract_param
($param, 'node');
3257 my $vmid = extract_param
($param, 'vmid');
3259 my $disk = extract_param
($param, 'disk');
3261 my $updatefn = sub {
3263 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3265 PVE
::QemuConfig-
>check_lock($conf);
3267 die "unable to create template, because VM contains snapshots\n"
3268 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3270 die "you can't convert a template to a template\n"
3271 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3273 die "you can't convert a VM to template if VM is running\n"
3274 if PVE
::QemuServer
::check_running
($vmid);
3277 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3280 $conf->{template
} = 1;
3281 PVE
::QemuConfig-
>write_config($vmid, $conf);
3283 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3286 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);