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 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
947 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
948 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
949 } elsif ($opt =~ m/^net(\d+)$/) {
951 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
952 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
956 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
958 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
960 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
964 my $conf = PVE
::QemuConfig-
>load_config($vmid);
966 die "checksum missmatch (file change by other user?)\n"
967 if $digest && $digest ne $conf->{digest
};
969 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
971 foreach my $opt (keys %$revert) {
972 if (defined($conf->{$opt})) {
973 $param->{$opt} = $conf->{$opt};
974 } elsif (defined($conf->{pending
}->{$opt})) {
979 if ($param->{memory
} || defined($param->{balloon
})) {
980 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
981 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
983 die "balloon value too large (must be smaller than assigned memory)\n"
984 if $balloon && $balloon > $maxmem;
987 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
991 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
993 # write updates to pending section
995 my $modified = {}; # record what $option we modify
997 foreach my $opt (@delete) {
998 $modified->{$opt} = 1;
999 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1000 if ($opt =~ m/^unused/) {
1001 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1002 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1003 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1004 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1005 delete $conf->{$opt};
1006 PVE
::QemuConfig-
>write_config($vmid, $conf);
1008 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1009 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1010 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1011 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1012 if defined($conf->{pending
}->{$opt});
1013 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1014 PVE
::QemuConfig-
>write_config($vmid, $conf);
1016 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1017 PVE
::QemuConfig-
>write_config($vmid, $conf);
1021 foreach my $opt (keys %$param) { # add/change
1022 $modified->{$opt} = 1;
1023 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1024 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1026 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1027 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1028 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1029 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1031 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1033 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1034 if defined($conf->{pending
}->{$opt});
1036 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1038 $conf->{pending
}->{$opt} = $param->{$opt};
1040 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1041 PVE
::QemuConfig-
>write_config($vmid, $conf);
1044 # remove pending changes when nothing changed
1045 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1046 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1047 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1049 return if !scalar(keys %{$conf->{pending
}});
1051 my $running = PVE
::QemuServer
::check_running
($vmid);
1053 # apply pending changes
1055 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1059 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1060 raise_param_exc
($errors) if scalar(keys %$errors);
1062 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1072 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1074 if ($background_delay) {
1076 # Note: It would be better to do that in the Event based HTTPServer
1077 # to avoid blocking call to sleep.
1079 my $end_time = time() + $background_delay;
1081 my $task = PVE
::Tools
::upid_decode
($upid);
1084 while (time() < $end_time) {
1085 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1087 sleep(1); # this gets interrupted when child process ends
1091 my $status = PVE
::Tools
::upid_read_status
($upid);
1092 return undef if $status eq 'OK';
1101 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1104 my $vm_config_perm_list = [
1109 'VM.Config.Network',
1111 'VM.Config.Options',
1114 __PACKAGE__-
>register_method({
1115 name
=> 'update_vm_async',
1116 path
=> '{vmid}/config',
1120 description
=> "Set virtual machine options (asynchrounous API).",
1122 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1125 additionalProperties
=> 0,
1126 properties
=> PVE
::QemuServer
::json_config_properties
(
1128 node
=> get_standard_option
('pve-node'),
1129 vmid
=> get_standard_option
('pve-vmid'),
1130 skiplock
=> get_standard_option
('skiplock'),
1132 type
=> 'string', format
=> 'pve-configid-list',
1133 description
=> "A list of settings you want to delete.",
1137 type
=> 'string', format
=> 'pve-configid-list',
1138 description
=> "Revert a pending change.",
1143 description
=> $opt_force_description,
1145 requires
=> 'delete',
1149 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1153 background_delay
=> {
1155 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1166 code
=> $update_vm_api,
1169 __PACKAGE__-
>register_method({
1170 name
=> 'update_vm',
1171 path
=> '{vmid}/config',
1175 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1177 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1180 additionalProperties
=> 0,
1181 properties
=> PVE
::QemuServer
::json_config_properties
(
1183 node
=> get_standard_option
('pve-node'),
1184 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1185 skiplock
=> get_standard_option
('skiplock'),
1187 type
=> 'string', format
=> 'pve-configid-list',
1188 description
=> "A list of settings you want to delete.",
1192 type
=> 'string', format
=> 'pve-configid-list',
1193 description
=> "Revert a pending change.",
1198 description
=> $opt_force_description,
1200 requires
=> 'delete',
1204 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1210 returns
=> { type
=> 'null' },
1213 &$update_vm_api($param, 1);
1219 __PACKAGE__-
>register_method({
1220 name
=> 'destroy_vm',
1225 description
=> "Destroy the vm (also delete all used/owned volumes).",
1227 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1230 additionalProperties
=> 0,
1232 node
=> get_standard_option
('pve-node'),
1233 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1234 skiplock
=> get_standard_option
('skiplock'),
1243 my $rpcenv = PVE
::RPCEnvironment
::get
();
1245 my $authuser = $rpcenv->get_user();
1247 my $vmid = $param->{vmid
};
1249 my $skiplock = $param->{skiplock
};
1250 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1251 if $skiplock && $authuser ne 'root@pam';
1254 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1256 my $storecfg = PVE
::Storage
::config
();
1258 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1260 die "unable to remove VM $vmid - used in HA resources\n"
1261 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1263 # early tests (repeat after locking)
1264 die "VM $vmid is running - destroy failed\n"
1265 if PVE
::QemuServer
::check_running
($vmid);
1270 syslog
('info', "destroy VM $vmid: $upid\n");
1272 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1274 PVE
::AccessControl
::remove_vm_access
($vmid);
1276 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1279 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1282 __PACKAGE__-
>register_method({
1284 path
=> '{vmid}/unlink',
1288 description
=> "Unlink/delete disk images.",
1290 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1293 additionalProperties
=> 0,
1295 node
=> get_standard_option
('pve-node'),
1296 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1298 type
=> 'string', format
=> 'pve-configid-list',
1299 description
=> "A list of disk IDs you want to delete.",
1303 description
=> $opt_force_description,
1308 returns
=> { type
=> 'null'},
1312 $param->{delete} = extract_param
($param, 'idlist');
1314 __PACKAGE__-
>update_vm($param);
1321 __PACKAGE__-
>register_method({
1323 path
=> '{vmid}/vncproxy',
1327 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1329 description
=> "Creates a TCP VNC proxy connections.",
1331 additionalProperties
=> 0,
1333 node
=> get_standard_option
('pve-node'),
1334 vmid
=> get_standard_option
('pve-vmid'),
1338 description
=> "starts websockify instead of vncproxy",
1343 additionalProperties
=> 0,
1345 user
=> { type
=> 'string' },
1346 ticket
=> { type
=> 'string' },
1347 cert
=> { type
=> 'string' },
1348 port
=> { type
=> 'integer' },
1349 upid
=> { type
=> 'string' },
1355 my $rpcenv = PVE
::RPCEnvironment
::get
();
1357 my $authuser = $rpcenv->get_user();
1359 my $vmid = $param->{vmid
};
1360 my $node = $param->{node
};
1361 my $websocket = $param->{websocket
};
1363 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1365 my $authpath = "/vms/$vmid";
1367 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1369 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1372 my ($remip, $family);
1375 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1376 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1377 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1378 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1380 $family = PVE
::Tools
::get_host_address_family
($node);
1383 my $port = PVE
::Tools
::next_vnc_port
($family);
1390 syslog
('info', "starting vnc proxy $upid\n");
1394 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1396 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1398 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1399 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1400 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1401 '-timeout', $timeout, '-authpath', $authpath,
1402 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1405 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1407 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1409 my $qmstr = join(' ', @$qmcmd);
1411 # also redirect stderr (else we get RFB protocol errors)
1412 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1415 PVE
::Tools
::run_command
($cmd);
1420 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1422 PVE
::Tools
::wait_for_vnc_port
($port);
1433 __PACKAGE__-
>register_method({
1434 name
=> 'vncwebsocket',
1435 path
=> '{vmid}/vncwebsocket',
1438 description
=> "You also need to pass a valid ticket (vncticket).",
1439 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1441 description
=> "Opens a weksocket for VNC traffic.",
1443 additionalProperties
=> 0,
1445 node
=> get_standard_option
('pve-node'),
1446 vmid
=> get_standard_option
('pve-vmid'),
1448 description
=> "Ticket from previous call to vncproxy.",
1453 description
=> "Port number returned by previous vncproxy call.",
1463 port
=> { type
=> 'string' },
1469 my $rpcenv = PVE
::RPCEnvironment
::get
();
1471 my $authuser = $rpcenv->get_user();
1473 my $vmid = $param->{vmid
};
1474 my $node = $param->{node
};
1476 my $authpath = "/vms/$vmid";
1478 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1480 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1482 # Note: VNC ports are acessible from outside, so we do not gain any
1483 # security if we verify that $param->{port} belongs to VM $vmid. This
1484 # check is done by verifying the VNC ticket (inside VNC protocol).
1486 my $port = $param->{port
};
1488 return { port
=> $port };
1491 __PACKAGE__-
>register_method({
1492 name
=> 'spiceproxy',
1493 path
=> '{vmid}/spiceproxy',
1498 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1500 description
=> "Returns a SPICE configuration to connect to the VM.",
1502 additionalProperties
=> 0,
1504 node
=> get_standard_option
('pve-node'),
1505 vmid
=> get_standard_option
('pve-vmid'),
1506 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1509 returns
=> get_standard_option
('remote-viewer-config'),
1513 my $rpcenv = PVE
::RPCEnvironment
::get
();
1515 my $authuser = $rpcenv->get_user();
1517 my $vmid = $param->{vmid
};
1518 my $node = $param->{node
};
1519 my $proxy = $param->{proxy
};
1521 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1522 my $title = "VM $vmid";
1523 $title .= " - ". $conf->{name
} if $conf->{name
};
1525 my $port = PVE
::QemuServer
::spice_port
($vmid);
1527 my ($ticket, undef, $remote_viewer_config) =
1528 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1530 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1531 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1533 return $remote_viewer_config;
1536 __PACKAGE__-
>register_method({
1538 path
=> '{vmid}/status',
1541 description
=> "Directory index",
1546 additionalProperties
=> 0,
1548 node
=> get_standard_option
('pve-node'),
1549 vmid
=> get_standard_option
('pve-vmid'),
1557 subdir
=> { type
=> 'string' },
1560 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1566 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1569 { subdir
=> 'current' },
1570 { subdir
=> 'start' },
1571 { subdir
=> 'stop' },
1577 __PACKAGE__-
>register_method({
1578 name
=> 'vm_status',
1579 path
=> '{vmid}/status/current',
1582 protected
=> 1, # qemu pid files are only readable by root
1583 description
=> "Get virtual machine status.",
1585 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1588 additionalProperties
=> 0,
1590 node
=> get_standard_option
('pve-node'),
1591 vmid
=> get_standard_option
('pve-vmid'),
1594 returns
=> { type
=> 'object' },
1599 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1601 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1602 my $status = $vmstatus->{$param->{vmid
}};
1604 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1606 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1611 __PACKAGE__-
>register_method({
1613 path
=> '{vmid}/status/start',
1617 description
=> "Start virtual machine.",
1619 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1622 additionalProperties
=> 0,
1624 node
=> get_standard_option
('pve-node'),
1625 vmid
=> get_standard_option
('pve-vmid',
1626 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1627 skiplock
=> get_standard_option
('skiplock'),
1628 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1629 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1632 enum
=> ['secure', 'insecure'],
1633 description
=> "Migration traffic is encrypted using an SSH " .
1634 "tunnel by default. On secure, completely private networks " .
1635 "this can be disabled to increase performance.",
1638 migration_network
=> {
1639 type
=> 'string', format
=> 'CIDR',
1640 description
=> "CIDR of the (sub) network that is used for migration.",
1643 machine
=> get_standard_option
('pve-qm-machine'),
1652 my $rpcenv = PVE
::RPCEnvironment
::get
();
1654 my $authuser = $rpcenv->get_user();
1656 my $node = extract_param
($param, 'node');
1658 my $vmid = extract_param
($param, 'vmid');
1660 my $machine = extract_param
($param, 'machine');
1662 my $stateuri = extract_param
($param, 'stateuri');
1663 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1664 if $stateuri && $authuser ne 'root@pam';
1666 my $skiplock = extract_param
($param, 'skiplock');
1667 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1668 if $skiplock && $authuser ne 'root@pam';
1670 my $migratedfrom = extract_param
($param, 'migratedfrom');
1671 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1672 if $migratedfrom && $authuser ne 'root@pam';
1674 my $migration_type = extract_param
($param, 'migration_type');
1675 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1676 if $migration_type && $authuser ne 'root@pam';
1678 my $migration_network = extract_param
($param, 'migration_network');
1679 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1680 if $migration_network && $authuser ne 'root@pam';
1682 # read spice ticket from STDIN
1684 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1685 if (defined(my $line = <>)) {
1687 $spice_ticket = $line;
1691 PVE
::Cluster
::check_cfs_quorum
();
1693 my $storecfg = PVE
::Storage
::config
();
1695 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1696 $rpcenv->{type
} ne 'ha') {
1701 my $service = "vm:$vmid";
1703 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1705 print "Executing HA start for VM $vmid\n";
1707 PVE
::Tools
::run_command
($cmd);
1712 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1719 syslog
('info', "start VM $vmid: $upid\n");
1721 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1722 $machine, $spice_ticket, $migration_network, $migration_type);
1727 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1731 __PACKAGE__-
>register_method({
1733 path
=> '{vmid}/status/stop',
1737 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1738 "is akin to pulling the power plug of a running computer and may damage the VM data",
1740 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1743 additionalProperties
=> 0,
1745 node
=> get_standard_option
('pve-node'),
1746 vmid
=> get_standard_option
('pve-vmid',
1747 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1748 skiplock
=> get_standard_option
('skiplock'),
1749 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1751 description
=> "Wait maximal timeout seconds.",
1757 description
=> "Do not deactivate storage volumes.",
1770 my $rpcenv = PVE
::RPCEnvironment
::get
();
1772 my $authuser = $rpcenv->get_user();
1774 my $node = extract_param
($param, 'node');
1776 my $vmid = extract_param
($param, 'vmid');
1778 my $skiplock = extract_param
($param, 'skiplock');
1779 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1780 if $skiplock && $authuser ne 'root@pam';
1782 my $keepActive = extract_param
($param, 'keepActive');
1783 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1784 if $keepActive && $authuser ne 'root@pam';
1786 my $migratedfrom = extract_param
($param, 'migratedfrom');
1787 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1788 if $migratedfrom && $authuser ne 'root@pam';
1791 my $storecfg = PVE
::Storage
::config
();
1793 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1798 my $service = "vm:$vmid";
1800 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1802 print "Executing HA stop for VM $vmid\n";
1804 PVE
::Tools
::run_command
($cmd);
1809 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1815 syslog
('info', "stop VM $vmid: $upid\n");
1817 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1818 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1823 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1827 __PACKAGE__-
>register_method({
1829 path
=> '{vmid}/status/reset',
1833 description
=> "Reset virtual machine.",
1835 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1838 additionalProperties
=> 0,
1840 node
=> get_standard_option
('pve-node'),
1841 vmid
=> get_standard_option
('pve-vmid',
1842 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1843 skiplock
=> get_standard_option
('skiplock'),
1852 my $rpcenv = PVE
::RPCEnvironment
::get
();
1854 my $authuser = $rpcenv->get_user();
1856 my $node = extract_param
($param, 'node');
1858 my $vmid = extract_param
($param, 'vmid');
1860 my $skiplock = extract_param
($param, 'skiplock');
1861 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1862 if $skiplock && $authuser ne 'root@pam';
1864 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1869 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1874 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1877 __PACKAGE__-
>register_method({
1878 name
=> 'vm_shutdown',
1879 path
=> '{vmid}/status/shutdown',
1883 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1884 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1886 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1889 additionalProperties
=> 0,
1891 node
=> get_standard_option
('pve-node'),
1892 vmid
=> get_standard_option
('pve-vmid',
1893 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1894 skiplock
=> get_standard_option
('skiplock'),
1896 description
=> "Wait maximal timeout seconds.",
1902 description
=> "Make sure the VM stops.",
1908 description
=> "Do not deactivate storage volumes.",
1921 my $rpcenv = PVE
::RPCEnvironment
::get
();
1923 my $authuser = $rpcenv->get_user();
1925 my $node = extract_param
($param, 'node');
1927 my $vmid = extract_param
($param, 'vmid');
1929 my $skiplock = extract_param
($param, 'skiplock');
1930 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1931 if $skiplock && $authuser ne 'root@pam';
1933 my $keepActive = extract_param
($param, 'keepActive');
1934 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1935 if $keepActive && $authuser ne 'root@pam';
1937 my $storecfg = PVE
::Storage
::config
();
1941 # if vm is paused, do not shutdown (but stop if forceStop = 1)
1942 # otherwise, we will infer a shutdown command, but run into the timeout,
1943 # then when the vm is resumed, it will instantly shutdown
1945 # checking the qmp status here to get feedback to the gui/cli/api
1946 # and the status query should not take too long
1949 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
1953 if (!$err && $qmpstatus->{status
} eq "paused") {
1954 if ($param->{forceStop
}) {
1955 warn "VM is paused - stop instead of shutdown\n";
1958 die "VM is paused - cannot shutdown\n";
1965 syslog
('info', "shutdown VM $vmid: $upid\n");
1967 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1968 $shutdown, $param->{forceStop
}, $keepActive);
1973 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1976 __PACKAGE__-
>register_method({
1977 name
=> 'vm_suspend',
1978 path
=> '{vmid}/status/suspend',
1982 description
=> "Suspend virtual machine.",
1984 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1987 additionalProperties
=> 0,
1989 node
=> get_standard_option
('pve-node'),
1990 vmid
=> get_standard_option
('pve-vmid',
1991 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1992 skiplock
=> get_standard_option
('skiplock'),
2001 my $rpcenv = PVE
::RPCEnvironment
::get
();
2003 my $authuser = $rpcenv->get_user();
2005 my $node = extract_param
($param, 'node');
2007 my $vmid = extract_param
($param, 'vmid');
2009 my $skiplock = extract_param
($param, 'skiplock');
2010 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2011 if $skiplock && $authuser ne 'root@pam';
2013 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2018 syslog
('info', "suspend VM $vmid: $upid\n");
2020 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2025 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2028 __PACKAGE__-
>register_method({
2029 name
=> 'vm_resume',
2030 path
=> '{vmid}/status/resume',
2034 description
=> "Resume virtual machine.",
2036 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2039 additionalProperties
=> 0,
2041 node
=> get_standard_option
('pve-node'),
2042 vmid
=> get_standard_option
('pve-vmid',
2043 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2044 skiplock
=> get_standard_option
('skiplock'),
2045 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2055 my $rpcenv = PVE
::RPCEnvironment
::get
();
2057 my $authuser = $rpcenv->get_user();
2059 my $node = extract_param
($param, 'node');
2061 my $vmid = extract_param
($param, 'vmid');
2063 my $skiplock = extract_param
($param, 'skiplock');
2064 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2065 if $skiplock && $authuser ne 'root@pam';
2067 my $nocheck = extract_param
($param, 'nocheck');
2069 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2074 syslog
('info', "resume VM $vmid: $upid\n");
2076 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2081 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2084 __PACKAGE__-
>register_method({
2085 name
=> 'vm_sendkey',
2086 path
=> '{vmid}/sendkey',
2090 description
=> "Send key event to virtual machine.",
2092 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2095 additionalProperties
=> 0,
2097 node
=> get_standard_option
('pve-node'),
2098 vmid
=> get_standard_option
('pve-vmid',
2099 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2100 skiplock
=> get_standard_option
('skiplock'),
2102 description
=> "The key (qemu monitor encoding).",
2107 returns
=> { type
=> 'null'},
2111 my $rpcenv = PVE
::RPCEnvironment
::get
();
2113 my $authuser = $rpcenv->get_user();
2115 my $node = extract_param
($param, 'node');
2117 my $vmid = extract_param
($param, 'vmid');
2119 my $skiplock = extract_param
($param, 'skiplock');
2120 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2121 if $skiplock && $authuser ne 'root@pam';
2123 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2128 __PACKAGE__-
>register_method({
2129 name
=> 'vm_feature',
2130 path
=> '{vmid}/feature',
2134 description
=> "Check if feature for virtual machine is available.",
2136 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2139 additionalProperties
=> 0,
2141 node
=> get_standard_option
('pve-node'),
2142 vmid
=> get_standard_option
('pve-vmid'),
2144 description
=> "Feature to check.",
2146 enum
=> [ 'snapshot', 'clone', 'copy' ],
2148 snapname
=> get_standard_option
('pve-snapshot-name', {
2156 hasFeature
=> { type
=> 'boolean' },
2159 items
=> { type
=> 'string' },
2166 my $node = extract_param
($param, 'node');
2168 my $vmid = extract_param
($param, 'vmid');
2170 my $snapname = extract_param
($param, 'snapname');
2172 my $feature = extract_param
($param, 'feature');
2174 my $running = PVE
::QemuServer
::check_running
($vmid);
2176 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2179 my $snap = $conf->{snapshots
}->{$snapname};
2180 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2183 my $storecfg = PVE
::Storage
::config
();
2185 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2186 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2189 hasFeature
=> $hasFeature,
2190 nodes
=> [ keys %$nodelist ],
2194 __PACKAGE__-
>register_method({
2196 path
=> '{vmid}/clone',
2200 description
=> "Create a copy of virtual machine/template.",
2202 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2203 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2204 "'Datastore.AllocateSpace' on any used storage.",
2207 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2209 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2210 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2215 additionalProperties
=> 0,
2217 node
=> get_standard_option
('pve-node'),
2218 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2219 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2222 type
=> 'string', format
=> 'dns-name',
2223 description
=> "Set a name for the new VM.",
2228 description
=> "Description for the new VM.",
2232 type
=> 'string', format
=> 'pve-poolid',
2233 description
=> "Add the new VM to the specified pool.",
2235 snapname
=> get_standard_option
('pve-snapshot-name', {
2238 storage
=> get_standard_option
('pve-storage-id', {
2239 description
=> "Target storage for full clone.",
2244 description
=> "Target format for file storage.",
2248 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2253 description
=> "Create a full copy of all disk. This is always done when " .
2254 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2257 target
=> get_standard_option
('pve-node', {
2258 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2269 my $rpcenv = PVE
::RPCEnvironment
::get
();
2271 my $authuser = $rpcenv->get_user();
2273 my $node = extract_param
($param, 'node');
2275 my $vmid = extract_param
($param, 'vmid');
2277 my $newid = extract_param
($param, 'newid');
2279 my $pool = extract_param
($param, 'pool');
2281 if (defined($pool)) {
2282 $rpcenv->check_pool_exist($pool);
2285 my $snapname = extract_param
($param, 'snapname');
2287 my $storage = extract_param
($param, 'storage');
2289 my $format = extract_param
($param, 'format');
2291 my $target = extract_param
($param, 'target');
2293 my $localnode = PVE
::INotify
::nodename
();
2295 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2297 PVE
::Cluster
::check_node_exists
($target) if $target;
2299 my $storecfg = PVE
::Storage
::config
();
2302 # check if storage is enabled on local node
2303 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2305 # check if storage is available on target node
2306 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2307 # clone only works if target storage is shared
2308 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2309 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2313 PVE
::Cluster
::check_cfs_quorum
();
2315 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2317 # exclusive lock if VM is running - else shared lock is enough;
2318 my $shared_lock = $running ?
0 : 1;
2322 # do all tests after lock
2323 # we also try to do all tests before we fork the worker
2325 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2327 PVE
::QemuConfig-
>check_lock($conf);
2329 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2331 die "unexpected state change\n" if $verify_running != $running;
2333 die "snapshot '$snapname' does not exist\n"
2334 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2336 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2338 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2340 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2342 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2344 die "unable to create VM $newid: config file already exists\n"
2347 my $newconf = { lock => 'clone' };
2352 foreach my $opt (keys %$oldconf) {
2353 my $value = $oldconf->{$opt};
2355 # do not copy snapshot related info
2356 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2357 $opt eq 'vmstate' || $opt eq 'snapstate';
2359 # no need to copy unused images, because VMID(owner) changes anyways
2360 next if $opt =~ m/^unused\d+$/;
2362 # always change MAC! address
2363 if ($opt =~ m/^net(\d+)$/) {
2364 my $net = PVE
::QemuServer
::parse_net
($value);
2365 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2366 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2367 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2368 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2369 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2370 die "unable to parse drive options for '$opt'\n" if !$drive;
2371 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2372 $newconf->{$opt} = $value; # simply copy configuration
2374 if ($param->{full
}) {
2375 die "Full clone feature is not available"
2376 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2377 $fullclone->{$opt} = 1;
2379 # not full means clone instead of copy
2380 die "Linked clone feature is not available"
2381 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2383 $drives->{$opt} = $drive;
2384 push @$vollist, $drive->{file
};
2387 # copy everything else
2388 $newconf->{$opt} = $value;
2392 # auto generate a new uuid
2393 my ($uuid, $uuid_str);
2394 UUID
::generate
($uuid);
2395 UUID
::unparse
($uuid, $uuid_str);
2396 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2397 $smbios1->{uuid
} = $uuid_str;
2398 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2400 delete $newconf->{template
};
2402 if ($param->{name
}) {
2403 $newconf->{name
} = $param->{name
};
2405 if ($oldconf->{name
}) {
2406 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2408 $newconf->{name
} = "Copy-of-VM-$vmid";
2412 if ($param->{description
}) {
2413 $newconf->{description
} = $param->{description
};
2416 # create empty/temp config - this fails if VM already exists on other node
2417 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2422 my $newvollist = [];
2425 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2427 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2429 foreach my $opt (keys %$drives) {
2430 my $drive = $drives->{$opt};
2432 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2433 $newid, $storage, $format, $fullclone->{$opt}, $newvollist);
2435 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2437 PVE
::QemuConfig-
>write_config($newid, $newconf);
2440 delete $newconf->{lock};
2441 PVE
::QemuConfig-
>write_config($newid, $newconf);
2444 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2445 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2446 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2448 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2449 die "Failed to move config to node '$target' - rename failed: $!\n"
2450 if !rename($conffile, $newconffile);
2453 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2458 sleep 1; # some storage like rbd need to wait before release volume - really?
2460 foreach my $volid (@$newvollist) {
2461 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2464 die "clone failed: $err";
2470 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2472 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2475 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2476 # Aquire exclusive lock lock for $newid
2477 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2482 __PACKAGE__-
>register_method({
2483 name
=> 'move_vm_disk',
2484 path
=> '{vmid}/move_disk',
2488 description
=> "Move volume to different storage.",
2490 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2491 "and 'Datastore.AllocateSpace' permissions on the storage.",
2494 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2495 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2499 additionalProperties
=> 0,
2501 node
=> get_standard_option
('pve-node'),
2502 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2505 description
=> "The disk you want to move.",
2506 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2508 storage
=> get_standard_option
('pve-storage-id', {
2509 description
=> "Target storage.",
2510 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2514 description
=> "Target Format.",
2515 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2520 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2526 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2534 description
=> "the task ID.",
2539 my $rpcenv = PVE
::RPCEnvironment
::get
();
2541 my $authuser = $rpcenv->get_user();
2543 my $node = extract_param
($param, 'node');
2545 my $vmid = extract_param
($param, 'vmid');
2547 my $digest = extract_param
($param, 'digest');
2549 my $disk = extract_param
($param, 'disk');
2551 my $storeid = extract_param
($param, 'storage');
2553 my $format = extract_param
($param, 'format');
2555 my $storecfg = PVE
::Storage
::config
();
2557 my $updatefn = sub {
2559 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2561 PVE
::QemuConfig-
>check_lock($conf);
2563 die "checksum missmatch (file change by other user?)\n"
2564 if $digest && $digest ne $conf->{digest
};
2566 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2568 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2570 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2572 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2575 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2576 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2580 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2581 (!$format || !$oldfmt || $oldfmt eq $format);
2583 # this only checks snapshots because $disk is passed!
2584 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2585 die "you can't move a disk with snapshots and delete the source\n"
2586 if $snapshotted && $param->{delete};
2588 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2590 my $running = PVE
::QemuServer
::check_running
($vmid);
2592 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2596 my $newvollist = [];
2599 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2601 warn "moving disk with snapshots, snapshots will not be moved!\n"
2604 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2605 $vmid, $storeid, $format, 1, $newvollist);
2607 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2609 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2611 PVE
::QemuConfig-
>write_config($vmid, $conf);
2614 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2615 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2622 foreach my $volid (@$newvollist) {
2623 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2626 die "storage migration failed: $err";
2629 if ($param->{delete}) {
2631 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2632 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2638 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2641 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2644 __PACKAGE__-
>register_method({
2645 name
=> 'migrate_vm',
2646 path
=> '{vmid}/migrate',
2650 description
=> "Migrate virtual machine. Creates a new migration task.",
2652 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2655 additionalProperties
=> 0,
2657 node
=> get_standard_option
('pve-node'),
2658 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2659 target
=> get_standard_option
('pve-node', {
2660 description
=> "Target node.",
2661 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2665 description
=> "Use online/live migration.",
2670 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2675 enum
=> ['secure', 'insecure'],
2676 description
=> "Migration traffic is encrypted using an SSH " .
2677 "tunnel by default. On secure, completely private networks " .
2678 "this can be disabled to increase performance.",
2681 migration_network
=> {
2684 description
=> "CIDR of the (sub) network that is used for migration.",
2691 description
=> "the task ID.",
2696 my $rpcenv = PVE
::RPCEnvironment
::get
();
2698 my $authuser = $rpcenv->get_user();
2700 my $target = extract_param
($param, 'target');
2702 my $localnode = PVE
::INotify
::nodename
();
2703 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2705 PVE
::Cluster
::check_cfs_quorum
();
2707 PVE
::Cluster
::check_node_exists
($target);
2709 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2711 my $vmid = extract_param
($param, 'vmid');
2713 raise_param_exc
({ force
=> "Only root may use this option." })
2714 if $param->{force
} && $authuser ne 'root@pam';
2716 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2717 if $param->{migration_type
} && $authuser ne 'root@pam';
2719 # allow root only until better network permissions are available
2720 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2721 if $param->{migration_network
} && $authuser ne 'root@pam';
2724 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2726 # try to detect errors early
2728 PVE
::QemuConfig-
>check_lock($conf);
2730 if (PVE
::QemuServer
::check_running
($vmid)) {
2731 die "cant migrate running VM without --online\n"
2732 if !$param->{online
};
2735 my $storecfg = PVE
::Storage
::config
();
2736 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2738 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2743 my $service = "vm:$vmid";
2745 my $cmd = ['ha-manager', 'migrate', $service, $target];
2747 print "Executing HA migrate for VM $vmid to node $target\n";
2749 PVE
::Tools
::run_command
($cmd);
2754 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2761 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2764 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2769 __PACKAGE__-
>register_method({
2771 path
=> '{vmid}/monitor',
2775 description
=> "Execute Qemu monitor commands.",
2777 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2780 additionalProperties
=> 0,
2782 node
=> get_standard_option
('pve-node'),
2783 vmid
=> get_standard_option
('pve-vmid'),
2786 description
=> "The monitor command.",
2790 returns
=> { type
=> 'string'},
2794 my $vmid = $param->{vmid
};
2796 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2800 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2802 $res = "ERROR: $@" if $@;
2807 __PACKAGE__-
>register_method({
2808 name
=> 'resize_vm',
2809 path
=> '{vmid}/resize',
2813 description
=> "Extend volume size.",
2815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2818 additionalProperties
=> 0,
2820 node
=> get_standard_option
('pve-node'),
2821 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2822 skiplock
=> get_standard_option
('skiplock'),
2825 description
=> "The disk you want to resize.",
2826 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
2830 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2831 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.",
2835 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2841 returns
=> { type
=> 'null'},
2845 my $rpcenv = PVE
::RPCEnvironment
::get
();
2847 my $authuser = $rpcenv->get_user();
2849 my $node = extract_param
($param, 'node');
2851 my $vmid = extract_param
($param, 'vmid');
2853 my $digest = extract_param
($param, 'digest');
2855 my $disk = extract_param
($param, 'disk');
2857 my $sizestr = extract_param
($param, 'size');
2859 my $skiplock = extract_param
($param, 'skiplock');
2860 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2861 if $skiplock && $authuser ne 'root@pam';
2863 my $storecfg = PVE
::Storage
::config
();
2865 my $updatefn = sub {
2867 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2869 die "checksum missmatch (file change by other user?)\n"
2870 if $digest && $digest ne $conf->{digest
};
2871 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
2873 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2875 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2877 my (undef, undef, undef, undef, undef, undef, $format) =
2878 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2880 die "can't resize volume: $disk if snapshot exists\n"
2881 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2883 my $volid = $drive->{file
};
2885 die "disk '$disk' has no associated volume\n" if !$volid;
2887 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2889 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2891 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2893 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2894 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2896 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2897 my ($ext, $newsize, $unit) = ($1, $2, $4);
2900 $newsize = $newsize * 1024;
2901 } elsif ($unit eq 'M') {
2902 $newsize = $newsize * 1024 * 1024;
2903 } elsif ($unit eq 'G') {
2904 $newsize = $newsize * 1024 * 1024 * 1024;
2905 } elsif ($unit eq 'T') {
2906 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2909 $newsize += $size if $ext;
2910 $newsize = int($newsize);
2912 die "unable to skrink disk size\n" if $newsize < $size;
2914 return if $size == $newsize;
2916 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2918 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2920 $drive->{size
} = $newsize;
2921 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2923 PVE
::QemuConfig-
>write_config($vmid, $conf);
2926 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2930 __PACKAGE__-
>register_method({
2931 name
=> 'snapshot_list',
2932 path
=> '{vmid}/snapshot',
2934 description
=> "List all snapshots.",
2936 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2939 protected
=> 1, # qemu pid files are only readable by root
2941 additionalProperties
=> 0,
2943 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2944 node
=> get_standard_option
('pve-node'),
2953 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2958 my $vmid = $param->{vmid
};
2960 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2961 my $snaphash = $conf->{snapshots
} || {};
2965 foreach my $name (keys %$snaphash) {
2966 my $d = $snaphash->{$name};
2969 snaptime
=> $d->{snaptime
} || 0,
2970 vmstate
=> $d->{vmstate
} ?
1 : 0,
2971 description
=> $d->{description
} || '',
2973 $item->{parent
} = $d->{parent
} if $d->{parent
};
2974 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2978 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2979 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2980 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2982 push @$res, $current;
2987 __PACKAGE__-
>register_method({
2989 path
=> '{vmid}/snapshot',
2993 description
=> "Snapshot a VM.",
2995 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2998 additionalProperties
=> 0,
3000 node
=> get_standard_option
('pve-node'),
3001 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3002 snapname
=> get_standard_option
('pve-snapshot-name'),
3006 description
=> "Save the vmstate",
3011 description
=> "A textual description or comment.",
3017 description
=> "the task ID.",
3022 my $rpcenv = PVE
::RPCEnvironment
::get
();
3024 my $authuser = $rpcenv->get_user();
3026 my $node = extract_param
($param, 'node');
3028 my $vmid = extract_param
($param, 'vmid');
3030 my $snapname = extract_param
($param, 'snapname');
3032 die "unable to use snapshot name 'current' (reserved name)\n"
3033 if $snapname eq 'current';
3036 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3037 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3038 $param->{description
});
3041 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3044 __PACKAGE__-
>register_method({
3045 name
=> 'snapshot_cmd_idx',
3046 path
=> '{vmid}/snapshot/{snapname}',
3053 additionalProperties
=> 0,
3055 vmid
=> get_standard_option
('pve-vmid'),
3056 node
=> get_standard_option
('pve-node'),
3057 snapname
=> get_standard_option
('pve-snapshot-name'),
3066 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3073 push @$res, { cmd
=> 'rollback' };
3074 push @$res, { cmd
=> 'config' };
3079 __PACKAGE__-
>register_method({
3080 name
=> 'update_snapshot_config',
3081 path
=> '{vmid}/snapshot/{snapname}/config',
3085 description
=> "Update snapshot metadata.",
3087 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3090 additionalProperties
=> 0,
3092 node
=> get_standard_option
('pve-node'),
3093 vmid
=> get_standard_option
('pve-vmid'),
3094 snapname
=> get_standard_option
('pve-snapshot-name'),
3098 description
=> "A textual description or comment.",
3102 returns
=> { type
=> 'null' },
3106 my $rpcenv = PVE
::RPCEnvironment
::get
();
3108 my $authuser = $rpcenv->get_user();
3110 my $vmid = extract_param
($param, 'vmid');
3112 my $snapname = extract_param
($param, 'snapname');
3114 return undef if !defined($param->{description
});
3116 my $updatefn = sub {
3118 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3120 PVE
::QemuConfig-
>check_lock($conf);
3122 my $snap = $conf->{snapshots
}->{$snapname};
3124 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3126 $snap->{description
} = $param->{description
} if defined($param->{description
});
3128 PVE
::QemuConfig-
>write_config($vmid, $conf);
3131 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3136 __PACKAGE__-
>register_method({
3137 name
=> 'get_snapshot_config',
3138 path
=> '{vmid}/snapshot/{snapname}/config',
3141 description
=> "Get snapshot configuration",
3143 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3146 additionalProperties
=> 0,
3148 node
=> get_standard_option
('pve-node'),
3149 vmid
=> get_standard_option
('pve-vmid'),
3150 snapname
=> get_standard_option
('pve-snapshot-name'),
3153 returns
=> { type
=> "object" },
3157 my $rpcenv = PVE
::RPCEnvironment
::get
();
3159 my $authuser = $rpcenv->get_user();
3161 my $vmid = extract_param
($param, 'vmid');
3163 my $snapname = extract_param
($param, 'snapname');
3165 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3167 my $snap = $conf->{snapshots
}->{$snapname};
3169 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3174 __PACKAGE__-
>register_method({
3176 path
=> '{vmid}/snapshot/{snapname}/rollback',
3180 description
=> "Rollback VM state to specified 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'),
3194 description
=> "the task ID.",
3199 my $rpcenv = PVE
::RPCEnvironment
::get
();
3201 my $authuser = $rpcenv->get_user();
3203 my $node = extract_param
($param, 'node');
3205 my $vmid = extract_param
($param, 'vmid');
3207 my $snapname = extract_param
($param, 'snapname');
3210 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3211 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3214 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3217 __PACKAGE__-
>register_method({
3218 name
=> 'delsnapshot',
3219 path
=> '{vmid}/snapshot/{snapname}',
3223 description
=> "Delete a VM snapshot.",
3225 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3228 additionalProperties
=> 0,
3230 node
=> get_standard_option
('pve-node'),
3231 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3232 snapname
=> get_standard_option
('pve-snapshot-name'),
3236 description
=> "For removal from config file, even if removing disk snapshots fails.",
3242 description
=> "the task ID.",
3247 my $rpcenv = PVE
::RPCEnvironment
::get
();
3249 my $authuser = $rpcenv->get_user();
3251 my $node = extract_param
($param, 'node');
3253 my $vmid = extract_param
($param, 'vmid');
3255 my $snapname = extract_param
($param, 'snapname');
3258 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3259 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3262 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3265 __PACKAGE__-
>register_method({
3267 path
=> '{vmid}/template',
3271 description
=> "Create a Template.",
3273 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3274 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3277 additionalProperties
=> 0,
3279 node
=> get_standard_option
('pve-node'),
3280 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3284 description
=> "If you want to convert only 1 disk to base image.",
3285 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3290 returns
=> { type
=> 'null'},
3294 my $rpcenv = PVE
::RPCEnvironment
::get
();
3296 my $authuser = $rpcenv->get_user();
3298 my $node = extract_param
($param, 'node');
3300 my $vmid = extract_param
($param, 'vmid');
3302 my $disk = extract_param
($param, 'disk');
3304 my $updatefn = sub {
3306 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3308 PVE
::QemuConfig-
>check_lock($conf);
3310 die "unable to create template, because VM contains snapshots\n"
3311 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3313 die "you can't convert a template to a template\n"
3314 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3316 die "you can't convert a VM to template if VM is running\n"
3317 if PVE
::QemuServer
::check_running
($vmid);
3320 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3323 $conf->{template
} = 1;
3324 PVE
::QemuConfig-
>write_config($vmid, $conf);
3326 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3329 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);