1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::ReplicationConfig
;
19 use PVE
::GuestHelpers
;
23 use PVE
::RPCEnvironment
;
24 use PVE
::AccessControl
;
28 use PVE
::API2
::Firewall
::VM
;
31 if (!$ENV{PVE_GENERATING_DOCS
}) {
32 require PVE
::HA
::Env
::PVE2
;
33 import PVE
::HA
::Env
::PVE2
;
34 require PVE
::HA
::Config
;
35 import PVE
::HA
::Config
;
39 use Data
::Dumper
; # fixme: remove
41 use base
qw(PVE::RESTHandler);
43 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.";
45 my $resolve_cdrom_alias = sub {
48 if (my $value = $param->{cdrom
}) {
49 $value .= ",media=cdrom" if $value !~ m/media=/;
50 $param->{ide2
} = $value;
51 delete $param->{cdrom
};
55 my $check_storage_access = sub {
56 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
58 PVE
::QemuServer
::foreach_drive
($settings, sub {
59 my ($ds, $drive) = @_;
61 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
63 my $volid = $drive->{file
};
65 if (!$volid || $volid eq 'none') {
67 } elsif ($isCDROM && ($volid eq 'cdrom')) {
68 $rpcenv->check($authuser, "/", ['Sys.Console']);
69 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
70 my ($storeid, $size) = ($2 || $default_storage, $3);
71 die "no storage ID specified (and no default storage)\n" if !$storeid;
72 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
74 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
79 my $check_storage_access_clone = sub {
80 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
84 PVE
::QemuServer
::foreach_drive
($conf, sub {
85 my ($ds, $drive) = @_;
87 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
89 my $volid = $drive->{file
};
91 return if !$volid || $volid eq 'none';
94 if ($volid eq 'cdrom') {
95 $rpcenv->check($authuser, "/", ['Sys.Console']);
97 # we simply allow access
98 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
99 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
100 $sharedvm = 0 if !$scfg->{shared
};
104 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
105 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
106 $sharedvm = 0 if !$scfg->{shared
};
108 $sid = $storage if $storage;
109 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
116 # Note: $pool is only needed when creating a VM, because pool permissions
117 # are automatically inherited if VM already exists inside a pool.
118 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
119 my $create_disks = sub {
120 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
127 my ($ds, $disk) = @_;
129 my $volid = $disk->{file
};
131 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
132 delete $disk->{size
};
133 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
134 } elsif ($volid =~ $NEW_DISK_RE) {
135 my ($storeid, $size) = ($2 || $default_storage, $3);
136 die "no storage ID specified (and no default storage)\n" if !$storeid;
137 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
138 my $fmt = $disk->{format
} || $defformat;
141 if ($ds eq 'efidisk0') {
143 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
144 die "uefi vars image not found\n" if ! -f
$ovmfvars;
145 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
147 $disk->{file
} = $volid;
148 $disk->{size
} = 128*1024;
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
152 my $path = PVE
::Storage
::path
($storecfg, $volid);
153 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
154 push @$efidiskcmd, $ovmfvars;
155 push @$efidiskcmd, $path;
157 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
159 eval { PVE
::Tools
::run_command
($efidiskcmd); };
161 die "Copying of EFI Vars image failed: $err" if $err;
163 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
164 $fmt, undef, $size*1024*1024);
165 $disk->{file
} = $volid;
166 $disk->{size
} = $size*1024*1024*1024;
168 push @$vollist, $volid;
169 delete $disk->{format
}; # no longer needed
170 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
173 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
175 my $volid_is_new = 1;
178 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
179 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
184 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
186 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
188 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
190 die "volume $volid does not exists\n" if !$size;
192 $disk->{size
} = $size;
195 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
199 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
201 # free allocated images on error
203 syslog
('err', "VM $vmid creating disks failed");
204 foreach my $volid (@$vollist) {
205 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
211 # modify vm config if everything went well
212 foreach my $ds (keys %$res) {
213 $conf->{$ds} = $res->{$ds};
230 my $memoryoptions = {
236 my $hwtypeoptions = {
248 my $generaloptions = {
255 'migrate_downtime' => 1,
256 'migrate_speed' => 1,
268 my $vmpoweroptions = {
277 my $check_vm_modify_config_perm = sub {
278 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
280 return 1 if $authuser eq 'root@pam';
282 foreach my $opt (@$key_list) {
283 # disk checks need to be done somewhere else
284 next if PVE
::QemuServer
::is_valid_drivename
($opt);
285 next if $opt eq 'cdrom';
286 next if $opt =~ m/^unused\d+$/;
288 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
289 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
290 } elsif ($memoryoptions->{$opt}) {
291 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
292 } elsif ($hwtypeoptions->{$opt}) {
293 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
294 } elsif ($generaloptions->{$opt}) {
295 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
296 # special case for startup since it changes host behaviour
297 if ($opt eq 'startup') {
298 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
300 } elsif ($vmpoweroptions->{$opt}) {
301 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
302 } elsif ($diskoptions->{$opt}) {
303 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
304 } elsif ($opt =~ m/^net\d+$/) {
305 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
307 # catches usb\d+, hostpci\d+, args, lock, etc.
308 # new options will be checked here
309 die "only root can set '$opt' config\n";
316 __PACKAGE__-
>register_method({
320 description
=> "Virtual machine index (per node).",
322 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
326 protected
=> 1, # qemu pid files are only readable by root
328 additionalProperties
=> 0,
330 node
=> get_standard_option
('pve-node'),
334 description
=> "Determine the full status of active VMs.",
344 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
349 my $rpcenv = PVE
::RPCEnvironment
::get
();
350 my $authuser = $rpcenv->get_user();
352 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
355 foreach my $vmid (keys %$vmstatus) {
356 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
358 my $data = $vmstatus->{$vmid};
359 $data->{vmid
} = int($vmid);
368 __PACKAGE__-
>register_method({
372 description
=> "Create or restore a virtual machine.",
374 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
375 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
376 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
377 user
=> 'all', # check inside
382 additionalProperties
=> 0,
383 properties
=> PVE
::QemuServer
::json_config_properties
(
385 node
=> get_standard_option
('pve-node'),
386 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
388 description
=> "The backup file.",
392 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
394 storage
=> get_standard_option
('pve-storage-id', {
395 description
=> "Default storage.",
397 completion
=> \
&PVE
::QemuServer
::complete_storage
,
402 description
=> "Allow to overwrite existing VM.",
403 requires
=> 'archive',
408 description
=> "Assign a unique random ethernet address.",
409 requires
=> 'archive',
413 type
=> 'string', format
=> 'pve-poolid',
414 description
=> "Add the VM to the specified pool.",
424 my $rpcenv = PVE
::RPCEnvironment
::get
();
426 my $authuser = $rpcenv->get_user();
428 my $node = extract_param
($param, 'node');
430 my $vmid = extract_param
($param, 'vmid');
432 my $archive = extract_param
($param, 'archive');
434 my $storage = extract_param
($param, 'storage');
436 my $force = extract_param
($param, 'force');
438 my $unique = extract_param
($param, 'unique');
440 my $pool = extract_param
($param, 'pool');
442 my $filename = PVE
::QemuConfig-
>config_file($vmid);
444 my $storecfg = PVE
::Storage
::config
();
446 PVE
::Cluster
::check_cfs_quorum
();
448 if (defined($pool)) {
449 $rpcenv->check_pool_exist($pool);
452 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
453 if defined($storage);
455 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
457 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
459 } elsif ($archive && $force && (-f
$filename) &&
460 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
461 # OK: user has VM.Backup permissions, and want to restore an existing VM
467 &$resolve_cdrom_alias($param);
469 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
471 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
473 foreach my $opt (keys %$param) {
474 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
475 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
476 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
478 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
479 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
483 PVE
::QemuServer
::add_random_macs
($param);
485 my $keystr = join(' ', keys %$param);
486 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
488 if ($archive eq '-') {
489 die "pipe requires cli environment\n"
490 if $rpcenv->{type
} ne 'cli';
492 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
493 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
497 my $restorefn = sub {
498 my $vmlist = PVE
::Cluster
::get_vmlist
();
499 if ($vmlist->{ids
}->{$vmid}) {
500 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
501 if ($current_node eq $node) {
502 my $conf = PVE
::QemuConfig-
>load_config($vmid);
504 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
506 die "unable to restore vm $vmid - config file already exists\n"
509 die "unable to restore vm $vmid - vm is running\n"
510 if PVE
::QemuServer
::check_running
($vmid);
512 die "unable to restore vm $vmid - vm is a template\n"
513 if PVE
::QemuConfig-
>is_template($conf);
516 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
521 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
524 unique
=> $unique });
526 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
529 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
535 PVE
::Cluster
::check_vmid_unused
($vmid);
545 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
547 # try to be smart about bootdisk
548 my @disks = PVE
::QemuServer
::valid_drive_names
();
550 foreach my $ds (reverse @disks) {
551 next if !$conf->{$ds};
552 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
553 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
557 if (!$conf->{bootdisk
} && $firstdisk) {
558 $conf->{bootdisk
} = $firstdisk;
561 # auto generate uuid if user did not specify smbios1 option
562 if (!$conf->{smbios1
}) {
563 my ($uuid, $uuid_str);
564 UUID
::generate
($uuid);
565 UUID
::unparse
($uuid, $uuid_str);
566 $conf->{smbios1
} = "uuid=$uuid_str";
569 PVE
::QemuConfig-
>write_config($vmid, $conf);
575 foreach my $volid (@$vollist) {
576 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
579 die "create failed - $err";
582 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
585 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
588 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
591 __PACKAGE__-
>register_method({
596 description
=> "Directory index",
601 additionalProperties
=> 0,
603 node
=> get_standard_option
('pve-node'),
604 vmid
=> get_standard_option
('pve-vmid'),
612 subdir
=> { type
=> 'string' },
615 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
621 { subdir
=> 'config' },
622 { subdir
=> 'pending' },
623 { subdir
=> 'status' },
624 { subdir
=> 'unlink' },
625 { subdir
=> 'vncproxy' },
626 { subdir
=> 'migrate' },
627 { subdir
=> 'resize' },
628 { subdir
=> 'move' },
630 { subdir
=> 'rrddata' },
631 { subdir
=> 'monitor' },
632 { subdir
=> 'agent' },
633 { subdir
=> 'snapshot' },
634 { subdir
=> 'spiceproxy' },
635 { subdir
=> 'sendkey' },
636 { subdir
=> 'firewall' },
642 __PACKAGE__-
>register_method ({
643 subclass
=> "PVE::API2::Firewall::VM",
644 path
=> '{vmid}/firewall',
647 __PACKAGE__-
>register_method({
649 path
=> '{vmid}/rrd',
651 protected
=> 1, # fixme: can we avoid that?
653 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
655 description
=> "Read VM RRD statistics (returns PNG)",
657 additionalProperties
=> 0,
659 node
=> get_standard_option
('pve-node'),
660 vmid
=> get_standard_option
('pve-vmid'),
662 description
=> "Specify the time frame you are interested in.",
664 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
667 description
=> "The list of datasources you want to display.",
668 type
=> 'string', format
=> 'pve-configid-list',
671 description
=> "The RRD consolidation function",
673 enum
=> [ 'AVERAGE', 'MAX' ],
681 filename
=> { type
=> 'string' },
687 return PVE
::Cluster
::create_rrd_graph
(
688 "pve2-vm/$param->{vmid}", $param->{timeframe
},
689 $param->{ds
}, $param->{cf
});
693 __PACKAGE__-
>register_method({
695 path
=> '{vmid}/rrddata',
697 protected
=> 1, # fixme: can we avoid that?
699 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
701 description
=> "Read VM RRD statistics",
703 additionalProperties
=> 0,
705 node
=> get_standard_option
('pve-node'),
706 vmid
=> get_standard_option
('pve-vmid'),
708 description
=> "Specify the time frame you are interested in.",
710 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
713 description
=> "The RRD consolidation function",
715 enum
=> [ 'AVERAGE', 'MAX' ],
730 return PVE
::Cluster
::create_rrd_data
(
731 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
735 __PACKAGE__-
>register_method({
737 path
=> '{vmid}/config',
740 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
742 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
745 additionalProperties
=> 0,
747 node
=> get_standard_option
('pve-node'),
748 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
750 description
=> "Get current values (instead of pending values).",
762 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
769 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
771 delete $conf->{snapshots
};
773 if (!$param->{current
}) {
774 foreach my $opt (keys %{$conf->{pending
}}) {
775 next if $opt eq 'delete';
776 my $value = $conf->{pending
}->{$opt};
777 next if ref($value); # just to be sure
778 $conf->{$opt} = $value;
780 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
781 foreach my $opt (keys %$pending_delete_hash) {
782 delete $conf->{$opt} if $conf->{$opt};
786 delete $conf->{pending
};
791 __PACKAGE__-
>register_method({
792 name
=> 'vm_pending',
793 path
=> '{vmid}/pending',
796 description
=> "Get virtual machine configuration, including pending changes.",
798 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
801 additionalProperties
=> 0,
803 node
=> get_standard_option
('pve-node'),
804 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
813 description
=> "Configuration option name.",
817 description
=> "Current value.",
822 description
=> "Pending value.",
827 description
=> "Indicates a pending delete request if present and not 0. " .
828 "The value 2 indicates a force-delete request.",
840 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
842 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
846 foreach my $opt (keys %$conf) {
847 next if ref($conf->{$opt});
848 my $item = { key
=> $opt };
849 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
850 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
851 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
855 foreach my $opt (keys %{$conf->{pending
}}) {
856 next if $opt eq 'delete';
857 next if ref($conf->{pending
}->{$opt}); # just to be sure
858 next if defined($conf->{$opt});
859 my $item = { key
=> $opt };
860 $item->{pending
} = $conf->{pending
}->{$opt};
864 while (my ($opt, $force) = each %$pending_delete_hash) {
865 next if $conf->{pending
}->{$opt}; # just to be sure
866 next if $conf->{$opt};
867 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
874 # POST/PUT {vmid}/config implementation
876 # The original API used PUT (idempotent) an we assumed that all operations
877 # are fast. But it turned out that almost any configuration change can
878 # involve hot-plug actions, or disk alloc/free. Such actions can take long
879 # time to complete and have side effects (not idempotent).
881 # The new implementation uses POST and forks a worker process. We added
882 # a new option 'background_delay'. If specified we wait up to
883 # 'background_delay' second for the worker task to complete. It returns null
884 # if the task is finished within that time, else we return the UPID.
886 my $update_vm_api = sub {
887 my ($param, $sync) = @_;
889 my $rpcenv = PVE
::RPCEnvironment
::get
();
891 my $authuser = $rpcenv->get_user();
893 my $node = extract_param
($param, 'node');
895 my $vmid = extract_param
($param, 'vmid');
897 my $digest = extract_param
($param, 'digest');
899 my $background_delay = extract_param
($param, 'background_delay');
901 my @paramarr = (); # used for log message
902 foreach my $key (keys %$param) {
903 push @paramarr, "-$key", $param->{$key};
906 my $skiplock = extract_param
($param, 'skiplock');
907 raise_param_exc
({ skiplock
=> "Only root may use this option." })
908 if $skiplock && $authuser ne 'root@pam';
910 my $delete_str = extract_param
($param, 'delete');
912 my $revert_str = extract_param
($param, 'revert');
914 my $force = extract_param
($param, 'force');
916 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
918 my $storecfg = PVE
::Storage
::config
();
920 my $defaults = PVE
::QemuServer
::load_defaults
();
922 &$resolve_cdrom_alias($param);
924 # now try to verify all parameters
927 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
928 if (!PVE
::QemuServer
::option_exists
($opt)) {
929 raise_param_exc
({ revert
=> "unknown option '$opt'" });
932 raise_param_exc
({ delete => "you can't use '-$opt' and " .
933 "-revert $opt' at the same time" })
934 if defined($param->{$opt});
940 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
941 $opt = 'ide2' if $opt eq 'cdrom';
943 raise_param_exc
({ delete => "you can't use '-$opt' and " .
944 "-delete $opt' at the same time" })
945 if defined($param->{$opt});
947 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
948 "-revert $opt' at the same time" })
951 if (!PVE
::QemuServer
::option_exists
($opt)) {
952 raise_param_exc
({ delete => "unknown option '$opt'" });
958 my $repl_conf = PVE
::ReplicationConfig-
>new();
959 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
960 my $check_replication = sub {
962 return if !$is_replicated;
963 my $volid = $drive->{file
};
964 return if !$volid || !($drive->{replicate
}//1);
965 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
966 my ($storeid, $format);
967 if ($volid =~ $NEW_DISK_RE) {
969 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
971 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
972 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
974 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
975 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
976 return if $scfg->{shared
};
977 die "cannot add non-replicatable volume to a replicated VM\n";
980 foreach my $opt (keys %$param) {
981 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
983 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
984 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
985 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
986 $check_replication->($drive);
987 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
988 } elsif ($opt =~ m/^net(\d+)$/) {
990 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
991 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
995 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
997 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
999 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1001 my $updatefn = sub {
1003 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1005 die "checksum missmatch (file change by other user?)\n"
1006 if $digest && $digest ne $conf->{digest
};
1008 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1010 foreach my $opt (keys %$revert) {
1011 if (defined($conf->{$opt})) {
1012 $param->{$opt} = $conf->{$opt};
1013 } elsif (defined($conf->{pending
}->{$opt})) {
1018 if ($param->{memory
} || defined($param->{balloon
})) {
1019 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1020 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1022 die "balloon value too large (must be smaller than assigned memory)\n"
1023 if $balloon && $balloon > $maxmem;
1026 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1030 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1032 # write updates to pending section
1034 my $modified = {}; # record what $option we modify
1036 foreach my $opt (@delete) {
1037 $modified->{$opt} = 1;
1038 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1039 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1040 warn "cannot delete '$opt' - not set in current configuration!\n";
1041 $modified->{$opt} = 0;
1045 if ($opt =~ m/^unused/) {
1046 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1047 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1048 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1049 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1050 delete $conf->{$opt};
1051 PVE
::QemuConfig-
>write_config($vmid, $conf);
1053 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1054 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1055 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1056 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1057 if defined($conf->{pending
}->{$opt});
1058 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1059 PVE
::QemuConfig-
>write_config($vmid, $conf);
1061 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1062 PVE
::QemuConfig-
>write_config($vmid, $conf);
1066 foreach my $opt (keys %$param) { # add/change
1067 $modified->{$opt} = 1;
1068 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1069 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1071 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1072 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1073 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1074 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1076 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1078 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1079 if defined($conf->{pending
}->{$opt});
1081 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1083 $conf->{pending
}->{$opt} = $param->{$opt};
1085 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1086 PVE
::QemuConfig-
>write_config($vmid, $conf);
1089 # remove pending changes when nothing changed
1090 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1091 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1092 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1094 return if !scalar(keys %{$conf->{pending
}});
1096 my $running = PVE
::QemuServer
::check_running
($vmid);
1098 # apply pending changes
1100 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1104 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1105 raise_param_exc
($errors) if scalar(keys %$errors);
1107 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1117 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1119 if ($background_delay) {
1121 # Note: It would be better to do that in the Event based HTTPServer
1122 # to avoid blocking call to sleep.
1124 my $end_time = time() + $background_delay;
1126 my $task = PVE
::Tools
::upid_decode
($upid);
1129 while (time() < $end_time) {
1130 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1132 sleep(1); # this gets interrupted when child process ends
1136 my $status = PVE
::Tools
::upid_read_status
($upid);
1137 return undef if $status eq 'OK';
1146 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1149 my $vm_config_perm_list = [
1154 'VM.Config.Network',
1156 'VM.Config.Options',
1159 __PACKAGE__-
>register_method({
1160 name
=> 'update_vm_async',
1161 path
=> '{vmid}/config',
1165 description
=> "Set virtual machine options (asynchrounous API).",
1167 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1170 additionalProperties
=> 0,
1171 properties
=> PVE
::QemuServer
::json_config_properties
(
1173 node
=> get_standard_option
('pve-node'),
1174 vmid
=> get_standard_option
('pve-vmid'),
1175 skiplock
=> get_standard_option
('skiplock'),
1177 type
=> 'string', format
=> 'pve-configid-list',
1178 description
=> "A list of settings you want to delete.",
1182 type
=> 'string', format
=> 'pve-configid-list',
1183 description
=> "Revert a pending change.",
1188 description
=> $opt_force_description,
1190 requires
=> 'delete',
1194 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1198 background_delay
=> {
1200 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1211 code
=> $update_vm_api,
1214 __PACKAGE__-
>register_method({
1215 name
=> 'update_vm',
1216 path
=> '{vmid}/config',
1220 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1222 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1225 additionalProperties
=> 0,
1226 properties
=> PVE
::QemuServer
::json_config_properties
(
1228 node
=> get_standard_option
('pve-node'),
1229 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1230 skiplock
=> get_standard_option
('skiplock'),
1232 type
=> 'string', format
=> 'pve-configid-list',
1233 description
=> "A list of settings you want to delete.",
1237 type
=> 'string', format
=> 'pve-configid-list',
1238 description
=> "Revert a pending change.",
1243 description
=> $opt_force_description,
1245 requires
=> 'delete',
1249 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1255 returns
=> { type
=> 'null' },
1258 &$update_vm_api($param, 1);
1264 __PACKAGE__-
>register_method({
1265 name
=> 'destroy_vm',
1270 description
=> "Destroy the vm (also delete all used/owned volumes).",
1272 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1275 additionalProperties
=> 0,
1277 node
=> get_standard_option
('pve-node'),
1278 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1279 skiplock
=> get_standard_option
('skiplock'),
1288 my $rpcenv = PVE
::RPCEnvironment
::get
();
1290 my $authuser = $rpcenv->get_user();
1292 my $vmid = $param->{vmid
};
1294 my $skiplock = $param->{skiplock
};
1295 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1296 if $skiplock && $authuser ne 'root@pam';
1299 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1301 my $storecfg = PVE
::Storage
::config
();
1303 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1305 die "unable to remove VM $vmid - used in HA resources\n"
1306 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1308 # do not allow destroy if there are replication jobs
1309 my $repl_conf = PVE
::ReplicationConfig-
>new();
1310 $repl_conf->check_for_existing_jobs($vmid);
1312 # early tests (repeat after locking)
1313 die "VM $vmid is running - destroy failed\n"
1314 if PVE
::QemuServer
::check_running
($vmid);
1319 syslog
('info', "destroy VM $vmid: $upid\n");
1321 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1323 PVE
::AccessControl
::remove_vm_access
($vmid);
1325 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1328 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1331 __PACKAGE__-
>register_method({
1333 path
=> '{vmid}/unlink',
1337 description
=> "Unlink/delete disk images.",
1339 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1342 additionalProperties
=> 0,
1344 node
=> get_standard_option
('pve-node'),
1345 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1347 type
=> 'string', format
=> 'pve-configid-list',
1348 description
=> "A list of disk IDs you want to delete.",
1352 description
=> $opt_force_description,
1357 returns
=> { type
=> 'null'},
1361 $param->{delete} = extract_param
($param, 'idlist');
1363 __PACKAGE__-
>update_vm($param);
1370 __PACKAGE__-
>register_method({
1372 path
=> '{vmid}/vncproxy',
1376 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1378 description
=> "Creates a TCP VNC proxy connections.",
1380 additionalProperties
=> 0,
1382 node
=> get_standard_option
('pve-node'),
1383 vmid
=> get_standard_option
('pve-vmid'),
1387 description
=> "starts websockify instead of vncproxy",
1392 additionalProperties
=> 0,
1394 user
=> { type
=> 'string' },
1395 ticket
=> { type
=> 'string' },
1396 cert
=> { type
=> 'string' },
1397 port
=> { type
=> 'integer' },
1398 upid
=> { type
=> 'string' },
1404 my $rpcenv = PVE
::RPCEnvironment
::get
();
1406 my $authuser = $rpcenv->get_user();
1408 my $vmid = $param->{vmid
};
1409 my $node = $param->{node
};
1410 my $websocket = $param->{websocket
};
1412 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1414 my $authpath = "/vms/$vmid";
1416 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1418 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1421 my ($remip, $family);
1424 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1425 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1426 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1427 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1429 $family = PVE
::Tools
::get_host_address_family
($node);
1432 my $port = PVE
::Tools
::next_vnc_port
($family);
1439 syslog
('info', "starting vnc proxy $upid\n");
1443 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1445 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1447 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1448 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1449 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1450 '-timeout', $timeout, '-authpath', $authpath,
1451 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1452 PVE
::Tools
::run_command
($cmd);
1455 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1457 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1459 my $sock = IO
::Socket
::IP-
>new(
1464 GetAddrInfoFlags
=> 0,
1465 ) or die "failed to create socket: $!\n";
1466 # Inside the worker we shouldn't have any previous alarms
1467 # running anyway...:
1469 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1471 accept(my $cli, $sock) or die "connection failed: $!\n";
1474 if (PVE
::Tools
::run_command
($cmd,
1475 output
=> '>&'.fileno($cli),
1476 input
=> '<&'.fileno($cli),
1479 die "Failed to run vncproxy.\n";
1486 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1488 PVE
::Tools
::wait_for_vnc_port
($port);
1499 __PACKAGE__-
>register_method({
1500 name
=> 'vncwebsocket',
1501 path
=> '{vmid}/vncwebsocket',
1504 description
=> "You also need to pass a valid ticket (vncticket).",
1505 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1507 description
=> "Opens a weksocket for VNC traffic.",
1509 additionalProperties
=> 0,
1511 node
=> get_standard_option
('pve-node'),
1512 vmid
=> get_standard_option
('pve-vmid'),
1514 description
=> "Ticket from previous call to vncproxy.",
1519 description
=> "Port number returned by previous vncproxy call.",
1529 port
=> { type
=> 'string' },
1535 my $rpcenv = PVE
::RPCEnvironment
::get
();
1537 my $authuser = $rpcenv->get_user();
1539 my $vmid = $param->{vmid
};
1540 my $node = $param->{node
};
1542 my $authpath = "/vms/$vmid";
1544 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1546 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1548 # Note: VNC ports are acessible from outside, so we do not gain any
1549 # security if we verify that $param->{port} belongs to VM $vmid. This
1550 # check is done by verifying the VNC ticket (inside VNC protocol).
1552 my $port = $param->{port
};
1554 return { port
=> $port };
1557 __PACKAGE__-
>register_method({
1558 name
=> 'spiceproxy',
1559 path
=> '{vmid}/spiceproxy',
1564 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1566 description
=> "Returns a SPICE configuration to connect to the VM.",
1568 additionalProperties
=> 0,
1570 node
=> get_standard_option
('pve-node'),
1571 vmid
=> get_standard_option
('pve-vmid'),
1572 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1575 returns
=> get_standard_option
('remote-viewer-config'),
1579 my $rpcenv = PVE
::RPCEnvironment
::get
();
1581 my $authuser = $rpcenv->get_user();
1583 my $vmid = $param->{vmid
};
1584 my $node = $param->{node
};
1585 my $proxy = $param->{proxy
};
1587 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1588 my $title = "VM $vmid";
1589 $title .= " - ". $conf->{name
} if $conf->{name
};
1591 my $port = PVE
::QemuServer
::spice_port
($vmid);
1593 my ($ticket, undef, $remote_viewer_config) =
1594 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1596 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1597 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1599 return $remote_viewer_config;
1602 __PACKAGE__-
>register_method({
1604 path
=> '{vmid}/status',
1607 description
=> "Directory index",
1612 additionalProperties
=> 0,
1614 node
=> get_standard_option
('pve-node'),
1615 vmid
=> get_standard_option
('pve-vmid'),
1623 subdir
=> { type
=> 'string' },
1626 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1632 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1635 { subdir
=> 'current' },
1636 { subdir
=> 'start' },
1637 { subdir
=> 'stop' },
1643 __PACKAGE__-
>register_method({
1644 name
=> 'vm_status',
1645 path
=> '{vmid}/status/current',
1648 protected
=> 1, # qemu pid files are only readable by root
1649 description
=> "Get virtual machine status.",
1651 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1654 additionalProperties
=> 0,
1656 node
=> get_standard_option
('pve-node'),
1657 vmid
=> get_standard_option
('pve-vmid'),
1660 returns
=> { type
=> 'object' },
1665 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1667 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1668 my $status = $vmstatus->{$param->{vmid
}};
1670 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1672 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1677 __PACKAGE__-
>register_method({
1679 path
=> '{vmid}/status/start',
1683 description
=> "Start virtual machine.",
1685 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1688 additionalProperties
=> 0,
1690 node
=> get_standard_option
('pve-node'),
1691 vmid
=> get_standard_option
('pve-vmid',
1692 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1693 skiplock
=> get_standard_option
('skiplock'),
1694 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1695 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1698 enum
=> ['secure', 'insecure'],
1699 description
=> "Migration traffic is encrypted using an SSH " .
1700 "tunnel by default. On secure, completely private networks " .
1701 "this can be disabled to increase performance.",
1704 migration_network
=> {
1705 type
=> 'string', format
=> 'CIDR',
1706 description
=> "CIDR of the (sub) network that is used for migration.",
1709 machine
=> get_standard_option
('pve-qm-machine'),
1711 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1723 my $rpcenv = PVE
::RPCEnvironment
::get
();
1725 my $authuser = $rpcenv->get_user();
1727 my $node = extract_param
($param, 'node');
1729 my $vmid = extract_param
($param, 'vmid');
1731 my $machine = extract_param
($param, 'machine');
1733 my $stateuri = extract_param
($param, 'stateuri');
1734 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1735 if $stateuri && $authuser ne 'root@pam';
1737 my $skiplock = extract_param
($param, 'skiplock');
1738 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1739 if $skiplock && $authuser ne 'root@pam';
1741 my $migratedfrom = extract_param
($param, 'migratedfrom');
1742 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1743 if $migratedfrom && $authuser ne 'root@pam';
1745 my $migration_type = extract_param
($param, 'migration_type');
1746 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1747 if $migration_type && $authuser ne 'root@pam';
1749 my $migration_network = extract_param
($param, 'migration_network');
1750 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1751 if $migration_network && $authuser ne 'root@pam';
1753 my $targetstorage = extract_param
($param, 'targetstorage');
1754 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1755 if $targetstorage && $authuser ne 'root@pam';
1757 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1758 if $targetstorage && !$migratedfrom;
1760 # read spice ticket from STDIN
1762 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1763 if (defined(my $line = <>)) {
1765 $spice_ticket = $line;
1769 PVE
::Cluster
::check_cfs_quorum
();
1771 my $storecfg = PVE
::Storage
::config
();
1773 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1774 $rpcenv->{type
} ne 'ha') {
1779 my $service = "vm:$vmid";
1781 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1783 print "Requesting HA start for VM $vmid\n";
1785 PVE
::Tools
::run_command
($cmd);
1790 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1797 syslog
('info', "start VM $vmid: $upid\n");
1799 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1800 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1805 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1809 __PACKAGE__-
>register_method({
1811 path
=> '{vmid}/status/stop',
1815 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1816 "is akin to pulling the power plug of a running computer and may damage the VM data",
1818 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1821 additionalProperties
=> 0,
1823 node
=> get_standard_option
('pve-node'),
1824 vmid
=> get_standard_option
('pve-vmid',
1825 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1826 skiplock
=> get_standard_option
('skiplock'),
1827 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1829 description
=> "Wait maximal timeout seconds.",
1835 description
=> "Do not deactivate storage volumes.",
1848 my $rpcenv = PVE
::RPCEnvironment
::get
();
1850 my $authuser = $rpcenv->get_user();
1852 my $node = extract_param
($param, 'node');
1854 my $vmid = extract_param
($param, 'vmid');
1856 my $skiplock = extract_param
($param, 'skiplock');
1857 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1858 if $skiplock && $authuser ne 'root@pam';
1860 my $keepActive = extract_param
($param, 'keepActive');
1861 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1862 if $keepActive && $authuser ne 'root@pam';
1864 my $migratedfrom = extract_param
($param, 'migratedfrom');
1865 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1866 if $migratedfrom && $authuser ne 'root@pam';
1869 my $storecfg = PVE
::Storage
::config
();
1871 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1876 my $service = "vm:$vmid";
1878 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1880 print "Requesting HA stop for VM $vmid\n";
1882 PVE
::Tools
::run_command
($cmd);
1887 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1893 syslog
('info', "stop VM $vmid: $upid\n");
1895 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1896 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1901 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1905 __PACKAGE__-
>register_method({
1907 path
=> '{vmid}/status/reset',
1911 description
=> "Reset virtual machine.",
1913 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1916 additionalProperties
=> 0,
1918 node
=> get_standard_option
('pve-node'),
1919 vmid
=> get_standard_option
('pve-vmid',
1920 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1921 skiplock
=> get_standard_option
('skiplock'),
1930 my $rpcenv = PVE
::RPCEnvironment
::get
();
1932 my $authuser = $rpcenv->get_user();
1934 my $node = extract_param
($param, 'node');
1936 my $vmid = extract_param
($param, 'vmid');
1938 my $skiplock = extract_param
($param, 'skiplock');
1939 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1940 if $skiplock && $authuser ne 'root@pam';
1942 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1947 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1952 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1955 __PACKAGE__-
>register_method({
1956 name
=> 'vm_shutdown',
1957 path
=> '{vmid}/status/shutdown',
1961 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1962 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1964 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1967 additionalProperties
=> 0,
1969 node
=> get_standard_option
('pve-node'),
1970 vmid
=> get_standard_option
('pve-vmid',
1971 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1972 skiplock
=> get_standard_option
('skiplock'),
1974 description
=> "Wait maximal timeout seconds.",
1980 description
=> "Make sure the VM stops.",
1986 description
=> "Do not deactivate storage volumes.",
1999 my $rpcenv = PVE
::RPCEnvironment
::get
();
2001 my $authuser = $rpcenv->get_user();
2003 my $node = extract_param
($param, 'node');
2005 my $vmid = extract_param
($param, 'vmid');
2007 my $skiplock = extract_param
($param, 'skiplock');
2008 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2009 if $skiplock && $authuser ne 'root@pam';
2011 my $keepActive = extract_param
($param, 'keepActive');
2012 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2013 if $keepActive && $authuser ne 'root@pam';
2015 my $storecfg = PVE
::Storage
::config
();
2019 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2020 # otherwise, we will infer a shutdown command, but run into the timeout,
2021 # then when the vm is resumed, it will instantly shutdown
2023 # checking the qmp status here to get feedback to the gui/cli/api
2024 # and the status query should not take too long
2027 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2031 if (!$err && $qmpstatus->{status
} eq "paused") {
2032 if ($param->{forceStop
}) {
2033 warn "VM is paused - stop instead of shutdown\n";
2036 die "VM is paused - cannot shutdown\n";
2040 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2041 ($rpcenv->{type
} ne 'ha')) {
2046 my $service = "vm:$vmid";
2048 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2050 print "Requesting HA stop for VM $vmid\n";
2052 PVE
::Tools
::run_command
($cmd);
2057 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2064 syslog
('info', "shutdown VM $vmid: $upid\n");
2066 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2067 $shutdown, $param->{forceStop
}, $keepActive);
2072 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2076 __PACKAGE__-
>register_method({
2077 name
=> 'vm_suspend',
2078 path
=> '{vmid}/status/suspend',
2082 description
=> "Suspend virtual machine.",
2084 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2087 additionalProperties
=> 0,
2089 node
=> get_standard_option
('pve-node'),
2090 vmid
=> get_standard_option
('pve-vmid',
2091 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2092 skiplock
=> get_standard_option
('skiplock'),
2101 my $rpcenv = PVE
::RPCEnvironment
::get
();
2103 my $authuser = $rpcenv->get_user();
2105 my $node = extract_param
($param, 'node');
2107 my $vmid = extract_param
($param, 'vmid');
2109 my $skiplock = extract_param
($param, 'skiplock');
2110 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2111 if $skiplock && $authuser ne 'root@pam';
2113 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2118 syslog
('info', "suspend VM $vmid: $upid\n");
2120 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2125 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2128 __PACKAGE__-
>register_method({
2129 name
=> 'vm_resume',
2130 path
=> '{vmid}/status/resume',
2134 description
=> "Resume virtual machine.",
2136 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2139 additionalProperties
=> 0,
2141 node
=> get_standard_option
('pve-node'),
2142 vmid
=> get_standard_option
('pve-vmid',
2143 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2144 skiplock
=> get_standard_option
('skiplock'),
2145 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2155 my $rpcenv = PVE
::RPCEnvironment
::get
();
2157 my $authuser = $rpcenv->get_user();
2159 my $node = extract_param
($param, 'node');
2161 my $vmid = extract_param
($param, 'vmid');
2163 my $skiplock = extract_param
($param, 'skiplock');
2164 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2165 if $skiplock && $authuser ne 'root@pam';
2167 my $nocheck = extract_param
($param, 'nocheck');
2169 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2174 syslog
('info', "resume VM $vmid: $upid\n");
2176 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2181 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2184 __PACKAGE__-
>register_method({
2185 name
=> 'vm_sendkey',
2186 path
=> '{vmid}/sendkey',
2190 description
=> "Send key event to virtual machine.",
2192 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2195 additionalProperties
=> 0,
2197 node
=> get_standard_option
('pve-node'),
2198 vmid
=> get_standard_option
('pve-vmid',
2199 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2200 skiplock
=> get_standard_option
('skiplock'),
2202 description
=> "The key (qemu monitor encoding).",
2207 returns
=> { type
=> 'null'},
2211 my $rpcenv = PVE
::RPCEnvironment
::get
();
2213 my $authuser = $rpcenv->get_user();
2215 my $node = extract_param
($param, 'node');
2217 my $vmid = extract_param
($param, 'vmid');
2219 my $skiplock = extract_param
($param, 'skiplock');
2220 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2221 if $skiplock && $authuser ne 'root@pam';
2223 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2228 __PACKAGE__-
>register_method({
2229 name
=> 'vm_feature',
2230 path
=> '{vmid}/feature',
2234 description
=> "Check if feature for virtual machine is available.",
2236 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2239 additionalProperties
=> 0,
2241 node
=> get_standard_option
('pve-node'),
2242 vmid
=> get_standard_option
('pve-vmid'),
2244 description
=> "Feature to check.",
2246 enum
=> [ 'snapshot', 'clone', 'copy' ],
2248 snapname
=> get_standard_option
('pve-snapshot-name', {
2256 hasFeature
=> { type
=> 'boolean' },
2259 items
=> { type
=> 'string' },
2266 my $node = extract_param
($param, 'node');
2268 my $vmid = extract_param
($param, 'vmid');
2270 my $snapname = extract_param
($param, 'snapname');
2272 my $feature = extract_param
($param, 'feature');
2274 my $running = PVE
::QemuServer
::check_running
($vmid);
2276 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2279 my $snap = $conf->{snapshots
}->{$snapname};
2280 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2283 my $storecfg = PVE
::Storage
::config
();
2285 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2286 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2289 hasFeature
=> $hasFeature,
2290 nodes
=> [ keys %$nodelist ],
2294 __PACKAGE__-
>register_method({
2296 path
=> '{vmid}/clone',
2300 description
=> "Create a copy of virtual machine/template.",
2302 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2303 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2304 "'Datastore.AllocateSpace' on any used storage.",
2307 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2309 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2310 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2315 additionalProperties
=> 0,
2317 node
=> get_standard_option
('pve-node'),
2318 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2319 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2322 type
=> 'string', format
=> 'dns-name',
2323 description
=> "Set a name for the new VM.",
2328 description
=> "Description for the new VM.",
2332 type
=> 'string', format
=> 'pve-poolid',
2333 description
=> "Add the new VM to the specified pool.",
2335 snapname
=> get_standard_option
('pve-snapshot-name', {
2338 storage
=> get_standard_option
('pve-storage-id', {
2339 description
=> "Target storage for full clone.",
2344 description
=> "Target format for file storage.",
2348 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2353 description
=> "Create a full copy of all disk. This is always done when " .
2354 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2357 target
=> get_standard_option
('pve-node', {
2358 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2369 my $rpcenv = PVE
::RPCEnvironment
::get
();
2371 my $authuser = $rpcenv->get_user();
2373 my $node = extract_param
($param, 'node');
2375 my $vmid = extract_param
($param, 'vmid');
2377 my $newid = extract_param
($param, 'newid');
2379 my $pool = extract_param
($param, 'pool');
2381 if (defined($pool)) {
2382 $rpcenv->check_pool_exist($pool);
2385 my $snapname = extract_param
($param, 'snapname');
2387 my $storage = extract_param
($param, 'storage');
2389 my $format = extract_param
($param, 'format');
2391 my $target = extract_param
($param, 'target');
2393 my $localnode = PVE
::INotify
::nodename
();
2395 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2397 PVE
::Cluster
::check_node_exists
($target) if $target;
2399 my $storecfg = PVE
::Storage
::config
();
2402 # check if storage is enabled on local node
2403 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2405 # check if storage is available on target node
2406 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2407 # clone only works if target storage is shared
2408 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2409 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2413 PVE
::Cluster
::check_cfs_quorum
();
2415 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2417 # exclusive lock if VM is running - else shared lock is enough;
2418 my $shared_lock = $running ?
0 : 1;
2422 # do all tests after lock
2423 # we also try to do all tests before we fork the worker
2425 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2427 PVE
::QemuConfig-
>check_lock($conf);
2429 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2431 die "unexpected state change\n" if $verify_running != $running;
2433 die "snapshot '$snapname' does not exist\n"
2434 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2436 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2438 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2440 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2442 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2444 die "unable to create VM $newid: config file already exists\n"
2447 my $newconf = { lock => 'clone' };
2452 foreach my $opt (keys %$oldconf) {
2453 my $value = $oldconf->{$opt};
2455 # do not copy snapshot related info
2456 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2457 $opt eq 'vmstate' || $opt eq 'snapstate';
2459 # no need to copy unused images, because VMID(owner) changes anyways
2460 next if $opt =~ m/^unused\d+$/;
2462 # always change MAC! address
2463 if ($opt =~ m/^net(\d+)$/) {
2464 my $net = PVE
::QemuServer
::parse_net
($value);
2465 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2466 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2467 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2468 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2469 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2470 die "unable to parse drive options for '$opt'\n" if !$drive;
2471 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2472 $newconf->{$opt} = $value; # simply copy configuration
2474 if ($param->{full
}) {
2475 die "Full clone feature is not supported for drive '$opt'\n"
2476 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2477 $fullclone->{$opt} = 1;
2479 # not full means clone instead of copy
2480 die "Linked clone feature is not supported for drive '$opt'\n"
2481 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2483 $drives->{$opt} = $drive;
2484 push @$vollist, $drive->{file
};
2487 # copy everything else
2488 $newconf->{$opt} = $value;
2492 # auto generate a new uuid
2493 my ($uuid, $uuid_str);
2494 UUID
::generate
($uuid);
2495 UUID
::unparse
($uuid, $uuid_str);
2496 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2497 $smbios1->{uuid
} = $uuid_str;
2498 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2500 delete $newconf->{template
};
2502 if ($param->{name
}) {
2503 $newconf->{name
} = $param->{name
};
2505 if ($oldconf->{name
}) {
2506 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2508 $newconf->{name
} = "Copy-of-VM-$vmid";
2512 if ($param->{description
}) {
2513 $newconf->{description
} = $param->{description
};
2516 # create empty/temp config - this fails if VM already exists on other node
2517 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2522 my $newvollist = [];
2526 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2528 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2530 my $total_jobs = scalar(keys %{$drives});
2533 foreach my $opt (keys %$drives) {
2534 my $drive = $drives->{$opt};
2535 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2537 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2538 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2539 $jobs, $skipcomplete, $oldconf->{agent
});
2541 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2543 PVE
::QemuConfig-
>write_config($newid, $newconf);
2547 delete $newconf->{lock};
2548 PVE
::QemuConfig-
>write_config($newid, $newconf);
2551 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2552 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2553 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2555 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2556 die "Failed to move config to node '$target' - rename failed: $!\n"
2557 if !rename($conffile, $newconffile);
2560 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2565 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2567 sleep 1; # some storage like rbd need to wait before release volume - really?
2569 foreach my $volid (@$newvollist) {
2570 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2573 die "clone failed: $err";
2579 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2581 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2584 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2585 # Aquire exclusive lock lock for $newid
2586 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2591 __PACKAGE__-
>register_method({
2592 name
=> 'move_vm_disk',
2593 path
=> '{vmid}/move_disk',
2597 description
=> "Move volume to different storage.",
2599 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2601 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2602 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2606 additionalProperties
=> 0,
2608 node
=> get_standard_option
('pve-node'),
2609 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2612 description
=> "The disk you want to move.",
2613 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2615 storage
=> get_standard_option
('pve-storage-id', {
2616 description
=> "Target storage.",
2617 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2621 description
=> "Target Format.",
2622 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2627 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2633 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2641 description
=> "the task ID.",
2646 my $rpcenv = PVE
::RPCEnvironment
::get
();
2648 my $authuser = $rpcenv->get_user();
2650 my $node = extract_param
($param, 'node');
2652 my $vmid = extract_param
($param, 'vmid');
2654 my $digest = extract_param
($param, 'digest');
2656 my $disk = extract_param
($param, 'disk');
2658 my $storeid = extract_param
($param, 'storage');
2660 my $format = extract_param
($param, 'format');
2662 my $storecfg = PVE
::Storage
::config
();
2664 my $updatefn = sub {
2666 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2668 PVE
::QemuConfig-
>check_lock($conf);
2670 die "checksum missmatch (file change by other user?)\n"
2671 if $digest && $digest ne $conf->{digest
};
2673 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2675 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2677 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2679 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2682 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2683 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2687 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2688 (!$format || !$oldfmt || $oldfmt eq $format);
2690 # this only checks snapshots because $disk is passed!
2691 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2692 die "you can't move a disk with snapshots and delete the source\n"
2693 if $snapshotted && $param->{delete};
2695 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2697 my $running = PVE
::QemuServer
::check_running
($vmid);
2699 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2703 my $newvollist = [];
2706 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2708 warn "moving disk with snapshots, snapshots will not be moved!\n"
2711 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2712 $vmid, $storeid, $format, 1, $newvollist);
2714 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2716 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2718 # convert moved disk to base if part of template
2719 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2720 if PVE
::QemuConfig-
>is_template($conf);
2722 PVE
::QemuConfig-
>write_config($vmid, $conf);
2725 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2726 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2733 foreach my $volid (@$newvollist) {
2734 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2737 die "storage migration failed: $err";
2740 if ($param->{delete}) {
2742 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2743 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2749 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2752 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2755 __PACKAGE__-
>register_method({
2756 name
=> 'migrate_vm',
2757 path
=> '{vmid}/migrate',
2761 description
=> "Migrate virtual machine. Creates a new migration task.",
2763 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2766 additionalProperties
=> 0,
2768 node
=> get_standard_option
('pve-node'),
2769 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2770 target
=> get_standard_option
('pve-node', {
2771 description
=> "Target node.",
2772 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2776 description
=> "Use online/live migration.",
2781 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2786 enum
=> ['secure', 'insecure'],
2787 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2790 migration_network
=> {
2791 type
=> 'string', format
=> 'CIDR',
2792 description
=> "CIDR of the (sub) network that is used for migration.",
2795 "with-local-disks" => {
2797 description
=> "Enable live storage migration for local disk",
2800 targetstorage
=> get_standard_option
('pve-storage-id', {
2801 description
=> "Default target storage.",
2803 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2809 description
=> "the task ID.",
2814 my $rpcenv = PVE
::RPCEnvironment
::get
();
2816 my $authuser = $rpcenv->get_user();
2818 my $target = extract_param
($param, 'target');
2820 my $localnode = PVE
::INotify
::nodename
();
2821 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2823 PVE
::Cluster
::check_cfs_quorum
();
2825 PVE
::Cluster
::check_node_exists
($target);
2827 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2829 my $vmid = extract_param
($param, 'vmid');
2831 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2832 if !$param->{online
} && $param->{targetstorage
};
2834 raise_param_exc
({ force
=> "Only root may use this option." })
2835 if $param->{force
} && $authuser ne 'root@pam';
2837 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2838 if $param->{migration_type
} && $authuser ne 'root@pam';
2840 # allow root only until better network permissions are available
2841 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2842 if $param->{migration_network
} && $authuser ne 'root@pam';
2845 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2847 # try to detect errors early
2849 PVE
::QemuConfig-
>check_lock($conf);
2851 if (PVE
::QemuServer
::check_running
($vmid)) {
2852 die "cant migrate running VM without --online\n"
2853 if !$param->{online
};
2856 my $storecfg = PVE
::Storage
::config
();
2858 if( $param->{targetstorage
}) {
2859 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2861 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2864 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2869 my $service = "vm:$vmid";
2871 my $cmd = ['ha-manager', 'migrate', $service, $target];
2873 print "Requesting HA migration for VM $vmid to node $target\n";
2875 PVE
::Tools
::run_command
($cmd);
2880 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2885 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2889 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2892 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2897 __PACKAGE__-
>register_method({
2899 path
=> '{vmid}/monitor',
2903 description
=> "Execute Qemu monitor commands.",
2905 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2906 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2909 additionalProperties
=> 0,
2911 node
=> get_standard_option
('pve-node'),
2912 vmid
=> get_standard_option
('pve-vmid'),
2915 description
=> "The monitor command.",
2919 returns
=> { type
=> 'string'},
2923 my $rpcenv = PVE
::RPCEnvironment
::get
();
2924 my $authuser = $rpcenv->get_user();
2927 my $command = shift;
2928 return $command =~ m/^\s*info(\s+|$)/
2929 || $command =~ m/^\s*help\s*$/;
2932 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2933 if !&$is_ro($param->{command
});
2935 my $vmid = $param->{vmid
};
2937 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2941 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2943 $res = "ERROR: $@" if $@;
2948 my $guest_agent_commands = [
2956 'network-get-interfaces',
2959 'get-memory-blocks',
2960 'get-memory-block-info',
2967 __PACKAGE__-
>register_method({
2969 path
=> '{vmid}/agent',
2973 description
=> "Execute Qemu Guest Agent commands.",
2975 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2978 additionalProperties
=> 0,
2980 node
=> get_standard_option
('pve-node'),
2981 vmid
=> get_standard_option
('pve-vmid', {
2982 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2985 description
=> "The QGA command.",
2986 enum
=> $guest_agent_commands,
2992 description
=> "Returns an object with a single `result` property. The type of that
2993 property depends on the executed command.",
2998 my $vmid = $param->{vmid
};
3000 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3002 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3003 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3005 my $cmd = $param->{command
};
3007 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3009 return { result
=> $res };
3012 __PACKAGE__-
>register_method({
3013 name
=> 'resize_vm',
3014 path
=> '{vmid}/resize',
3018 description
=> "Extend volume size.",
3020 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3023 additionalProperties
=> 0,
3025 node
=> get_standard_option
('pve-node'),
3026 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3027 skiplock
=> get_standard_option
('skiplock'),
3030 description
=> "The disk you want to resize.",
3031 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3035 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3036 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.",
3040 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3046 returns
=> { type
=> 'null'},
3050 my $rpcenv = PVE
::RPCEnvironment
::get
();
3052 my $authuser = $rpcenv->get_user();
3054 my $node = extract_param
($param, 'node');
3056 my $vmid = extract_param
($param, 'vmid');
3058 my $digest = extract_param
($param, 'digest');
3060 my $disk = extract_param
($param, 'disk');
3062 my $sizestr = extract_param
($param, 'size');
3064 my $skiplock = extract_param
($param, 'skiplock');
3065 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3066 if $skiplock && $authuser ne 'root@pam';
3068 my $storecfg = PVE
::Storage
::config
();
3070 my $updatefn = sub {
3072 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3074 die "checksum missmatch (file change by other user?)\n"
3075 if $digest && $digest ne $conf->{digest
};
3076 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3078 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3080 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3082 my (undef, undef, undef, undef, undef, undef, $format) =
3083 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3085 die "can't resize volume: $disk if snapshot exists\n"
3086 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3088 my $volid = $drive->{file
};
3090 die "disk '$disk' has no associated volume\n" if !$volid;
3092 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3094 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3096 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3098 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3099 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3101 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3102 my ($ext, $newsize, $unit) = ($1, $2, $4);
3105 $newsize = $newsize * 1024;
3106 } elsif ($unit eq 'M') {
3107 $newsize = $newsize * 1024 * 1024;
3108 } elsif ($unit eq 'G') {
3109 $newsize = $newsize * 1024 * 1024 * 1024;
3110 } elsif ($unit eq 'T') {
3111 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3114 $newsize += $size if $ext;
3115 $newsize = int($newsize);
3117 die "shrinking disks is not supported\n" if $newsize < $size;
3119 return if $size == $newsize;
3121 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3123 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3125 $drive->{size
} = $newsize;
3126 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3128 PVE
::QemuConfig-
>write_config($vmid, $conf);
3131 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3135 __PACKAGE__-
>register_method({
3136 name
=> 'snapshot_list',
3137 path
=> '{vmid}/snapshot',
3139 description
=> "List all snapshots.",
3141 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3144 protected
=> 1, # qemu pid files are only readable by root
3146 additionalProperties
=> 0,
3148 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3149 node
=> get_standard_option
('pve-node'),
3158 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3163 my $vmid = $param->{vmid
};
3165 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3166 my $snaphash = $conf->{snapshots
} || {};
3170 foreach my $name (keys %$snaphash) {
3171 my $d = $snaphash->{$name};
3174 snaptime
=> $d->{snaptime
} || 0,
3175 vmstate
=> $d->{vmstate
} ?
1 : 0,
3176 description
=> $d->{description
} || '',
3178 $item->{parent
} = $d->{parent
} if $d->{parent
};
3179 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3183 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3184 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3185 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3187 push @$res, $current;
3192 __PACKAGE__-
>register_method({
3194 path
=> '{vmid}/snapshot',
3198 description
=> "Snapshot a VM.",
3200 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3203 additionalProperties
=> 0,
3205 node
=> get_standard_option
('pve-node'),
3206 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3207 snapname
=> get_standard_option
('pve-snapshot-name'),
3211 description
=> "Save the vmstate",
3216 description
=> "A textual description or comment.",
3222 description
=> "the task ID.",
3227 my $rpcenv = PVE
::RPCEnvironment
::get
();
3229 my $authuser = $rpcenv->get_user();
3231 my $node = extract_param
($param, 'node');
3233 my $vmid = extract_param
($param, 'vmid');
3235 my $snapname = extract_param
($param, 'snapname');
3237 die "unable to use snapshot name 'current' (reserved name)\n"
3238 if $snapname eq 'current';
3241 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3242 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3243 $param->{description
});
3246 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3249 __PACKAGE__-
>register_method({
3250 name
=> 'snapshot_cmd_idx',
3251 path
=> '{vmid}/snapshot/{snapname}',
3258 additionalProperties
=> 0,
3260 vmid
=> get_standard_option
('pve-vmid'),
3261 node
=> get_standard_option
('pve-node'),
3262 snapname
=> get_standard_option
('pve-snapshot-name'),
3271 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3278 push @$res, { cmd
=> 'rollback' };
3279 push @$res, { cmd
=> 'config' };
3284 __PACKAGE__-
>register_method({
3285 name
=> 'update_snapshot_config',
3286 path
=> '{vmid}/snapshot/{snapname}/config',
3290 description
=> "Update snapshot metadata.",
3292 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3295 additionalProperties
=> 0,
3297 node
=> get_standard_option
('pve-node'),
3298 vmid
=> get_standard_option
('pve-vmid'),
3299 snapname
=> get_standard_option
('pve-snapshot-name'),
3303 description
=> "A textual description or comment.",
3307 returns
=> { type
=> 'null' },
3311 my $rpcenv = PVE
::RPCEnvironment
::get
();
3313 my $authuser = $rpcenv->get_user();
3315 my $vmid = extract_param
($param, 'vmid');
3317 my $snapname = extract_param
($param, 'snapname');
3319 return undef if !defined($param->{description
});
3321 my $updatefn = sub {
3323 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3325 PVE
::QemuConfig-
>check_lock($conf);
3327 my $snap = $conf->{snapshots
}->{$snapname};
3329 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3331 $snap->{description
} = $param->{description
} if defined($param->{description
});
3333 PVE
::QemuConfig-
>write_config($vmid, $conf);
3336 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3341 __PACKAGE__-
>register_method({
3342 name
=> 'get_snapshot_config',
3343 path
=> '{vmid}/snapshot/{snapname}/config',
3346 description
=> "Get snapshot configuration",
3348 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3351 additionalProperties
=> 0,
3353 node
=> get_standard_option
('pve-node'),
3354 vmid
=> get_standard_option
('pve-vmid'),
3355 snapname
=> get_standard_option
('pve-snapshot-name'),
3358 returns
=> { type
=> "object" },
3362 my $rpcenv = PVE
::RPCEnvironment
::get
();
3364 my $authuser = $rpcenv->get_user();
3366 my $vmid = extract_param
($param, 'vmid');
3368 my $snapname = extract_param
($param, 'snapname');
3370 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3372 my $snap = $conf->{snapshots
}->{$snapname};
3374 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3379 __PACKAGE__-
>register_method({
3381 path
=> '{vmid}/snapshot/{snapname}/rollback',
3385 description
=> "Rollback VM state to specified snapshot.",
3387 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3390 additionalProperties
=> 0,
3392 node
=> get_standard_option
('pve-node'),
3393 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3394 snapname
=> get_standard_option
('pve-snapshot-name'),
3399 description
=> "the task ID.",
3404 my $rpcenv = PVE
::RPCEnvironment
::get
();
3406 my $authuser = $rpcenv->get_user();
3408 my $node = extract_param
($param, 'node');
3410 my $vmid = extract_param
($param, 'vmid');
3412 my $snapname = extract_param
($param, 'snapname');
3415 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3416 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3420 # hold migration lock, this makes sure that nobody create replication snapshots
3421 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3424 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3427 __PACKAGE__-
>register_method({
3428 name
=> 'delsnapshot',
3429 path
=> '{vmid}/snapshot/{snapname}',
3433 description
=> "Delete a VM snapshot.",
3435 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3438 additionalProperties
=> 0,
3440 node
=> get_standard_option
('pve-node'),
3441 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3442 snapname
=> get_standard_option
('pve-snapshot-name'),
3446 description
=> "For removal from config file, even if removing disk snapshots fails.",
3452 description
=> "the task ID.",
3457 my $rpcenv = PVE
::RPCEnvironment
::get
();
3459 my $authuser = $rpcenv->get_user();
3461 my $node = extract_param
($param, 'node');
3463 my $vmid = extract_param
($param, 'vmid');
3465 my $snapname = extract_param
($param, 'snapname');
3468 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3469 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3472 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3475 __PACKAGE__-
>register_method({
3477 path
=> '{vmid}/template',
3481 description
=> "Create a Template.",
3483 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3484 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3487 additionalProperties
=> 0,
3489 node
=> get_standard_option
('pve-node'),
3490 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3494 description
=> "If you want to convert only 1 disk to base image.",
3495 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3500 returns
=> { type
=> 'null'},
3504 my $rpcenv = PVE
::RPCEnvironment
::get
();
3506 my $authuser = $rpcenv->get_user();
3508 my $node = extract_param
($param, 'node');
3510 my $vmid = extract_param
($param, 'vmid');
3512 my $disk = extract_param
($param, 'disk');
3514 my $updatefn = sub {
3516 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3518 PVE
::QemuConfig-
>check_lock($conf);
3520 die "unable to create template, because VM contains snapshots\n"
3521 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3523 die "you can't convert a template to a template\n"
3524 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3526 die "you can't convert a VM to template if VM is running\n"
3527 if PVE
::QemuServer
::check_running
($vmid);
3530 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3533 $conf->{template
} = 1;
3534 PVE
::QemuConfig-
>write_config($vmid, $conf);
3536 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3539 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);