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 $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
56 my $check_storage_access = sub {
57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
59 PVE
::QemuServer
::foreach_drive
($settings, sub {
60 my ($ds, $drive) = @_;
62 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
64 my $volid = $drive->{file
};
66 if (!$volid || $volid eq 'none') {
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
70 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
74 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
75 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
76 if !$scfg->{content
}->{images
};
78 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
82 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
83 if defined($settings->{vmstatestorage
});
86 my $check_storage_access_clone = sub {
87 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
91 PVE
::QemuServer
::foreach_drive
($conf, sub {
92 my ($ds, $drive) = @_;
94 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
96 my $volid = $drive->{file
};
98 return if !$volid || $volid eq 'none';
101 if ($volid eq 'cdrom') {
102 $rpcenv->check($authuser, "/", ['Sys.Console']);
104 # we simply allow access
105 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
106 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
107 $sharedvm = 0 if !$scfg->{shared
};
111 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
113 $sharedvm = 0 if !$scfg->{shared
};
115 $sid = $storage if $storage;
116 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
120 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
121 if defined($conf->{vmstatestorage
});
126 # Note: $pool is only needed when creating a VM, because pool permissions
127 # are automatically inherited if VM already exists inside a pool.
128 my $create_disks = sub {
129 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
136 my ($ds, $disk) = @_;
138 my $volid = $disk->{file
};
140 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
141 delete $disk->{size
};
142 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
143 } elsif ($volid =~ $NEW_DISK_RE) {
144 my ($storeid, $size) = ($2 || $default_storage, $3);
145 die "no storage ID specified (and no default storage)\n" if !$storeid;
146 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
147 my $fmt = $disk->{format
} || $defformat;
149 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
152 if ($ds eq 'efidisk0') {
153 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt);
155 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
157 push @$vollist, $volid;
158 $disk->{file
} = $volid;
159 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
160 delete $disk->{format
}; # no longer needed
161 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
164 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
166 my $volid_is_new = 1;
169 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
170 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
175 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
177 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
179 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
181 die "volume $volid does not exists\n" if !$size;
183 $disk->{size
} = $size;
186 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
192 # free allocated images on error
194 syslog
('err', "VM $vmid creating disks failed");
195 foreach my $volid (@$vollist) {
196 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
202 # modify vm config if everything went well
203 foreach my $ds (keys %$res) {
204 $conf->{$ds} = $res->{$ds};
221 my $memoryoptions = {
227 my $hwtypeoptions = {
239 my $generaloptions = {
246 'migrate_downtime' => 1,
247 'migrate_speed' => 1,
259 my $vmpoweroptions = {
266 'vmstatestorage' => 1,
269 my $check_vm_modify_config_perm = sub {
270 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
272 return 1 if $authuser eq 'root@pam';
274 foreach my $opt (@$key_list) {
275 # disk checks need to be done somewhere else
276 next if PVE
::QemuServer
::is_valid_drivename
($opt);
277 next if $opt eq 'cdrom';
278 next if $opt =~ m/^unused\d+$/;
280 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
281 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
282 } elsif ($memoryoptions->{$opt}) {
283 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
284 } elsif ($hwtypeoptions->{$opt}) {
285 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
286 } elsif ($generaloptions->{$opt}) {
287 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
288 # special case for startup since it changes host behaviour
289 if ($opt eq 'startup') {
290 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
292 } elsif ($vmpoweroptions->{$opt}) {
293 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
294 } elsif ($diskoptions->{$opt}) {
295 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
296 } elsif ($opt =~ m/^net\d+$/) {
297 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
299 # catches usb\d+, hostpci\d+, args, lock, etc.
300 # new options will be checked here
301 die "only root can set '$opt' config\n";
308 __PACKAGE__-
>register_method({
312 description
=> "Virtual machine index (per node).",
314 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
318 protected
=> 1, # qemu pid files are only readable by root
320 additionalProperties
=> 0,
322 node
=> get_standard_option
('pve-node'),
326 description
=> "Determine the full status of active VMs.",
336 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
341 my $rpcenv = PVE
::RPCEnvironment
::get
();
342 my $authuser = $rpcenv->get_user();
344 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
347 foreach my $vmid (keys %$vmstatus) {
348 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
350 my $data = $vmstatus->{$vmid};
351 $data->{vmid
} = int($vmid);
360 __PACKAGE__-
>register_method({
364 description
=> "Create or restore a virtual machine.",
366 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
367 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
368 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
369 user
=> 'all', # check inside
374 additionalProperties
=> 0,
375 properties
=> PVE
::QemuServer
::json_config_properties
(
377 node
=> get_standard_option
('pve-node'),
378 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
380 description
=> "The backup file.",
384 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
386 storage
=> get_standard_option
('pve-storage-id', {
387 description
=> "Default storage.",
389 completion
=> \
&PVE
::QemuServer
::complete_storage
,
394 description
=> "Allow to overwrite existing VM.",
395 requires
=> 'archive',
400 description
=> "Assign a unique random ethernet address.",
401 requires
=> 'archive',
405 type
=> 'string', format
=> 'pve-poolid',
406 description
=> "Add the VM to the specified pool.",
416 my $rpcenv = PVE
::RPCEnvironment
::get
();
418 my $authuser = $rpcenv->get_user();
420 my $node = extract_param
($param, 'node');
422 my $vmid = extract_param
($param, 'vmid');
424 my $archive = extract_param
($param, 'archive');
426 my $storage = extract_param
($param, 'storage');
428 my $force = extract_param
($param, 'force');
430 my $unique = extract_param
($param, 'unique');
432 my $pool = extract_param
($param, 'pool');
434 my $filename = PVE
::QemuConfig-
>config_file($vmid);
436 my $storecfg = PVE
::Storage
::config
();
438 PVE
::Cluster
::check_cfs_quorum
();
440 if (defined($pool)) {
441 $rpcenv->check_pool_exist($pool);
444 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
445 if defined($storage);
447 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
449 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
451 } elsif ($archive && $force && (-f
$filename) &&
452 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
453 # OK: user has VM.Backup permissions, and want to restore an existing VM
459 &$resolve_cdrom_alias($param);
461 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
463 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
465 foreach my $opt (keys %$param) {
466 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
467 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
468 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
470 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
471 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
475 PVE
::QemuServer
::add_random_macs
($param);
477 my $keystr = join(' ', keys %$param);
478 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
480 if ($archive eq '-') {
481 die "pipe requires cli environment\n"
482 if $rpcenv->{type
} ne 'cli';
484 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
485 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
489 my $restorefn = sub {
490 my $vmlist = PVE
::Cluster
::get_vmlist
();
491 if ($vmlist->{ids
}->{$vmid}) {
492 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
493 if ($current_node eq $node) {
494 my $conf = PVE
::QemuConfig-
>load_config($vmid);
496 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
498 die "unable to restore vm $vmid - config file already exists\n"
501 die "unable to restore vm $vmid - vm is running\n"
502 if PVE
::QemuServer
::check_running
($vmid);
504 die "unable to restore vm $vmid - vm is a template\n"
505 if PVE
::QemuConfig-
>is_template($conf);
508 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
513 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
516 unique
=> $unique });
518 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
521 # ensure no old replication state are exists
522 PVE
::ReplicationState
::delete_guest_states
($vmid);
524 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
530 PVE
::Cluster
::check_vmid_unused
($vmid);
532 # ensure no old replication state are exists
533 PVE
::ReplicationState
::delete_guest_states
($vmid);
543 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
545 if (!$conf->{bootdisk
}) {
546 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
547 $conf->{bootdisk
} = $firstdisk if $firstdisk;
550 # auto generate uuid if user did not specify smbios1 option
551 if (!$conf->{smbios1
}) {
552 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
555 PVE
::QemuConfig-
>write_config($vmid, $conf);
561 foreach my $volid (@$vollist) {
562 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
565 die "create failed - $err";
568 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
571 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
574 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
577 __PACKAGE__-
>register_method({
582 description
=> "Directory index",
587 additionalProperties
=> 0,
589 node
=> get_standard_option
('pve-node'),
590 vmid
=> get_standard_option
('pve-vmid'),
598 subdir
=> { type
=> 'string' },
601 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
607 { subdir
=> 'config' },
608 { subdir
=> 'pending' },
609 { subdir
=> 'status' },
610 { subdir
=> 'unlink' },
611 { subdir
=> 'vncproxy' },
612 { subdir
=> 'migrate' },
613 { subdir
=> 'resize' },
614 { subdir
=> 'move' },
616 { subdir
=> 'rrddata' },
617 { subdir
=> 'monitor' },
618 { subdir
=> 'agent' },
619 { subdir
=> 'snapshot' },
620 { subdir
=> 'spiceproxy' },
621 { subdir
=> 'sendkey' },
622 { subdir
=> 'firewall' },
628 __PACKAGE__-
>register_method ({
629 subclass
=> "PVE::API2::Firewall::VM",
630 path
=> '{vmid}/firewall',
633 __PACKAGE__-
>register_method({
635 path
=> '{vmid}/rrd',
637 protected
=> 1, # fixme: can we avoid that?
639 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
641 description
=> "Read VM RRD statistics (returns PNG)",
643 additionalProperties
=> 0,
645 node
=> get_standard_option
('pve-node'),
646 vmid
=> get_standard_option
('pve-vmid'),
648 description
=> "Specify the time frame you are interested in.",
650 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
653 description
=> "The list of datasources you want to display.",
654 type
=> 'string', format
=> 'pve-configid-list',
657 description
=> "The RRD consolidation function",
659 enum
=> [ 'AVERAGE', 'MAX' ],
667 filename
=> { type
=> 'string' },
673 return PVE
::Cluster
::create_rrd_graph
(
674 "pve2-vm/$param->{vmid}", $param->{timeframe
},
675 $param->{ds
}, $param->{cf
});
679 __PACKAGE__-
>register_method({
681 path
=> '{vmid}/rrddata',
683 protected
=> 1, # fixme: can we avoid that?
685 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
687 description
=> "Read VM RRD statistics",
689 additionalProperties
=> 0,
691 node
=> get_standard_option
('pve-node'),
692 vmid
=> get_standard_option
('pve-vmid'),
694 description
=> "Specify the time frame you are interested in.",
696 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
699 description
=> "The RRD consolidation function",
701 enum
=> [ 'AVERAGE', 'MAX' ],
716 return PVE
::Cluster
::create_rrd_data
(
717 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
721 __PACKAGE__-
>register_method({
723 path
=> '{vmid}/config',
726 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
728 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
731 additionalProperties
=> 0,
733 node
=> get_standard_option
('pve-node'),
734 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
736 description
=> "Get current values (instead of pending values).",
748 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
755 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
757 delete $conf->{snapshots
};
759 if (!$param->{current
}) {
760 foreach my $opt (keys %{$conf->{pending
}}) {
761 next if $opt eq 'delete';
762 my $value = $conf->{pending
}->{$opt};
763 next if ref($value); # just to be sure
764 $conf->{$opt} = $value;
766 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
767 foreach my $opt (keys %$pending_delete_hash) {
768 delete $conf->{$opt} if $conf->{$opt};
772 delete $conf->{pending
};
777 __PACKAGE__-
>register_method({
778 name
=> 'vm_pending',
779 path
=> '{vmid}/pending',
782 description
=> "Get virtual machine configuration, including pending changes.",
784 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
787 additionalProperties
=> 0,
789 node
=> get_standard_option
('pve-node'),
790 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
799 description
=> "Configuration option name.",
803 description
=> "Current value.",
808 description
=> "Pending value.",
813 description
=> "Indicates a pending delete request if present and not 0. " .
814 "The value 2 indicates a force-delete request.",
826 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
828 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
832 foreach my $opt (keys %$conf) {
833 next if ref($conf->{$opt});
834 my $item = { key
=> $opt };
835 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
836 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
837 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
841 foreach my $opt (keys %{$conf->{pending
}}) {
842 next if $opt eq 'delete';
843 next if ref($conf->{pending
}->{$opt}); # just to be sure
844 next if defined($conf->{$opt});
845 my $item = { key
=> $opt };
846 $item->{pending
} = $conf->{pending
}->{$opt};
850 while (my ($opt, $force) = each %$pending_delete_hash) {
851 next if $conf->{pending
}->{$opt}; # just to be sure
852 next if $conf->{$opt};
853 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
860 # POST/PUT {vmid}/config implementation
862 # The original API used PUT (idempotent) an we assumed that all operations
863 # are fast. But it turned out that almost any configuration change can
864 # involve hot-plug actions, or disk alloc/free. Such actions can take long
865 # time to complete and have side effects (not idempotent).
867 # The new implementation uses POST and forks a worker process. We added
868 # a new option 'background_delay'. If specified we wait up to
869 # 'background_delay' second for the worker task to complete. It returns null
870 # if the task is finished within that time, else we return the UPID.
872 my $update_vm_api = sub {
873 my ($param, $sync) = @_;
875 my $rpcenv = PVE
::RPCEnvironment
::get
();
877 my $authuser = $rpcenv->get_user();
879 my $node = extract_param
($param, 'node');
881 my $vmid = extract_param
($param, 'vmid');
883 my $digest = extract_param
($param, 'digest');
885 my $background_delay = extract_param
($param, 'background_delay');
887 my @paramarr = (); # used for log message
888 foreach my $key (sort keys %$param) {
889 push @paramarr, "-$key", $param->{$key};
892 my $skiplock = extract_param
($param, 'skiplock');
893 raise_param_exc
({ skiplock
=> "Only root may use this option." })
894 if $skiplock && $authuser ne 'root@pam';
896 my $delete_str = extract_param
($param, 'delete');
898 my $revert_str = extract_param
($param, 'revert');
900 my $force = extract_param
($param, 'force');
902 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
904 my $storecfg = PVE
::Storage
::config
();
906 my $defaults = PVE
::QemuServer
::load_defaults
();
908 &$resolve_cdrom_alias($param);
910 # now try to verify all parameters
913 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
914 if (!PVE
::QemuServer
::option_exists
($opt)) {
915 raise_param_exc
({ revert
=> "unknown option '$opt'" });
918 raise_param_exc
({ delete => "you can't use '-$opt' and " .
919 "-revert $opt' at the same time" })
920 if defined($param->{$opt});
926 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
927 $opt = 'ide2' if $opt eq 'cdrom';
929 raise_param_exc
({ delete => "you can't use '-$opt' and " .
930 "-delete $opt' at the same time" })
931 if defined($param->{$opt});
933 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
934 "-revert $opt' at the same time" })
937 if (!PVE
::QemuServer
::option_exists
($opt)) {
938 raise_param_exc
({ delete => "unknown option '$opt'" });
944 my $repl_conf = PVE
::ReplicationConfig-
>new();
945 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
946 my $check_replication = sub {
948 return if !$is_replicated;
949 my $volid = $drive->{file
};
950 return if !$volid || !($drive->{replicate
}//1);
951 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
952 my ($storeid, $format);
953 if ($volid =~ $NEW_DISK_RE) {
955 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
957 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
958 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
960 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
961 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
962 return if $scfg->{shared
};
963 die "cannot add non-replicatable volume to a replicated VM\n";
966 foreach my $opt (keys %$param) {
967 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
969 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
970 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
971 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
972 $check_replication->($drive);
973 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
974 } elsif ($opt =~ m/^net(\d+)$/) {
976 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
977 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
981 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
983 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
985 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
989 my $conf = PVE
::QemuConfig-
>load_config($vmid);
991 die "checksum missmatch (file change by other user?)\n"
992 if $digest && $digest ne $conf->{digest
};
994 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
996 foreach my $opt (keys %$revert) {
997 if (defined($conf->{$opt})) {
998 $param->{$opt} = $conf->{$opt};
999 } elsif (defined($conf->{pending
}->{$opt})) {
1004 if ($param->{memory
} || defined($param->{balloon
})) {
1005 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1006 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1008 die "balloon value too large (must be smaller than assigned memory)\n"
1009 if $balloon && $balloon > $maxmem;
1012 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1016 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1018 # write updates to pending section
1020 my $modified = {}; # record what $option we modify
1022 foreach my $opt (@delete) {
1023 $modified->{$opt} = 1;
1024 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1025 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1026 warn "cannot delete '$opt' - not set in current configuration!\n";
1027 $modified->{$opt} = 0;
1031 if ($opt =~ m/^unused/) {
1032 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1033 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1034 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1035 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1036 delete $conf->{$opt};
1037 PVE
::QemuConfig-
>write_config($vmid, $conf);
1039 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1040 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1041 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1042 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1043 if defined($conf->{pending
}->{$opt});
1044 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1045 PVE
::QemuConfig-
>write_config($vmid, $conf);
1047 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1048 PVE
::QemuConfig-
>write_config($vmid, $conf);
1052 foreach my $opt (keys %$param) { # add/change
1053 $modified->{$opt} = 1;
1054 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1055 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1057 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1058 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1059 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1060 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1062 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1064 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1065 if defined($conf->{pending
}->{$opt});
1067 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1069 $conf->{pending
}->{$opt} = $param->{$opt};
1071 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1072 PVE
::QemuConfig-
>write_config($vmid, $conf);
1075 # remove pending changes when nothing changed
1076 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1077 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1078 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1080 return if !scalar(keys %{$conf->{pending
}});
1082 my $running = PVE
::QemuServer
::check_running
($vmid);
1084 # apply pending changes
1086 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1090 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1091 raise_param_exc
($errors) if scalar(keys %$errors);
1093 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1103 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1105 if ($background_delay) {
1107 # Note: It would be better to do that in the Event based HTTPServer
1108 # to avoid blocking call to sleep.
1110 my $end_time = time() + $background_delay;
1112 my $task = PVE
::Tools
::upid_decode
($upid);
1115 while (time() < $end_time) {
1116 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1118 sleep(1); # this gets interrupted when child process ends
1122 my $status = PVE
::Tools
::upid_read_status
($upid);
1123 return undef if $status eq 'OK';
1132 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1135 my $vm_config_perm_list = [
1140 'VM.Config.Network',
1142 'VM.Config.Options',
1145 __PACKAGE__-
>register_method({
1146 name
=> 'update_vm_async',
1147 path
=> '{vmid}/config',
1151 description
=> "Set virtual machine options (asynchrounous API).",
1153 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1156 additionalProperties
=> 0,
1157 properties
=> PVE
::QemuServer
::json_config_properties
(
1159 node
=> get_standard_option
('pve-node'),
1160 vmid
=> get_standard_option
('pve-vmid'),
1161 skiplock
=> get_standard_option
('skiplock'),
1163 type
=> 'string', format
=> 'pve-configid-list',
1164 description
=> "A list of settings you want to delete.",
1168 type
=> 'string', format
=> 'pve-configid-list',
1169 description
=> "Revert a pending change.",
1174 description
=> $opt_force_description,
1176 requires
=> 'delete',
1180 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1184 background_delay
=> {
1186 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1197 code
=> $update_vm_api,
1200 __PACKAGE__-
>register_method({
1201 name
=> 'update_vm',
1202 path
=> '{vmid}/config',
1206 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1208 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1211 additionalProperties
=> 0,
1212 properties
=> PVE
::QemuServer
::json_config_properties
(
1214 node
=> get_standard_option
('pve-node'),
1215 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1216 skiplock
=> get_standard_option
('skiplock'),
1218 type
=> 'string', format
=> 'pve-configid-list',
1219 description
=> "A list of settings you want to delete.",
1223 type
=> 'string', format
=> 'pve-configid-list',
1224 description
=> "Revert a pending change.",
1229 description
=> $opt_force_description,
1231 requires
=> 'delete',
1235 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1241 returns
=> { type
=> 'null' },
1244 &$update_vm_api($param, 1);
1250 __PACKAGE__-
>register_method({
1251 name
=> 'destroy_vm',
1256 description
=> "Destroy the vm (also delete all used/owned volumes).",
1258 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1261 additionalProperties
=> 0,
1263 node
=> get_standard_option
('pve-node'),
1264 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1265 skiplock
=> get_standard_option
('skiplock'),
1274 my $rpcenv = PVE
::RPCEnvironment
::get
();
1276 my $authuser = $rpcenv->get_user();
1278 my $vmid = $param->{vmid
};
1280 my $skiplock = $param->{skiplock
};
1281 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1282 if $skiplock && $authuser ne 'root@pam';
1285 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1287 my $storecfg = PVE
::Storage
::config
();
1289 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1291 die "unable to remove VM $vmid - used in HA resources\n"
1292 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1294 # do not allow destroy if there are replication jobs
1295 my $repl_conf = PVE
::ReplicationConfig-
>new();
1296 $repl_conf->check_for_existing_jobs($vmid);
1298 # early tests (repeat after locking)
1299 die "VM $vmid is running - destroy failed\n"
1300 if PVE
::QemuServer
::check_running
($vmid);
1305 syslog
('info', "destroy VM $vmid: $upid\n");
1307 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1309 PVE
::AccessControl
::remove_vm_access
($vmid);
1311 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1314 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1317 __PACKAGE__-
>register_method({
1319 path
=> '{vmid}/unlink',
1323 description
=> "Unlink/delete disk images.",
1325 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1328 additionalProperties
=> 0,
1330 node
=> get_standard_option
('pve-node'),
1331 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1333 type
=> 'string', format
=> 'pve-configid-list',
1334 description
=> "A list of disk IDs you want to delete.",
1338 description
=> $opt_force_description,
1343 returns
=> { type
=> 'null'},
1347 $param->{delete} = extract_param
($param, 'idlist');
1349 __PACKAGE__-
>update_vm($param);
1356 __PACKAGE__-
>register_method({
1358 path
=> '{vmid}/vncproxy',
1362 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1364 description
=> "Creates a TCP VNC proxy connections.",
1366 additionalProperties
=> 0,
1368 node
=> get_standard_option
('pve-node'),
1369 vmid
=> get_standard_option
('pve-vmid'),
1373 description
=> "starts websockify instead of vncproxy",
1378 additionalProperties
=> 0,
1380 user
=> { type
=> 'string' },
1381 ticket
=> { type
=> 'string' },
1382 cert
=> { type
=> 'string' },
1383 port
=> { type
=> 'integer' },
1384 upid
=> { type
=> 'string' },
1390 my $rpcenv = PVE
::RPCEnvironment
::get
();
1392 my $authuser = $rpcenv->get_user();
1394 my $vmid = $param->{vmid
};
1395 my $node = $param->{node
};
1396 my $websocket = $param->{websocket
};
1398 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1400 my $authpath = "/vms/$vmid";
1402 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1404 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1407 my ($remip, $family);
1410 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1411 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1412 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1413 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1415 $family = PVE
::Tools
::get_host_address_family
($node);
1418 my $port = PVE
::Tools
::next_vnc_port
($family);
1425 syslog
('info', "starting vnc proxy $upid\n");
1429 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1431 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1433 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1434 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1435 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1436 '-timeout', $timeout, '-authpath', $authpath,
1437 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1438 PVE
::Tools
::run_command
($cmd);
1441 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1443 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1445 my $sock = IO
::Socket
::IP-
>new(
1450 GetAddrInfoFlags
=> 0,
1451 ) or die "failed to create socket: $!\n";
1452 # Inside the worker we shouldn't have any previous alarms
1453 # running anyway...:
1455 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1457 accept(my $cli, $sock) or die "connection failed: $!\n";
1460 if (PVE
::Tools
::run_command
($cmd,
1461 output
=> '>&'.fileno($cli),
1462 input
=> '<&'.fileno($cli),
1465 die "Failed to run vncproxy.\n";
1472 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1474 PVE
::Tools
::wait_for_vnc_port
($port);
1485 __PACKAGE__-
>register_method({
1486 name
=> 'vncwebsocket',
1487 path
=> '{vmid}/vncwebsocket',
1490 description
=> "You also need to pass a valid ticket (vncticket).",
1491 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1493 description
=> "Opens a weksocket for VNC traffic.",
1495 additionalProperties
=> 0,
1497 node
=> get_standard_option
('pve-node'),
1498 vmid
=> get_standard_option
('pve-vmid'),
1500 description
=> "Ticket from previous call to vncproxy.",
1505 description
=> "Port number returned by previous vncproxy call.",
1515 port
=> { type
=> 'string' },
1521 my $rpcenv = PVE
::RPCEnvironment
::get
();
1523 my $authuser = $rpcenv->get_user();
1525 my $vmid = $param->{vmid
};
1526 my $node = $param->{node
};
1528 my $authpath = "/vms/$vmid";
1530 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1532 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1534 # Note: VNC ports are acessible from outside, so we do not gain any
1535 # security if we verify that $param->{port} belongs to VM $vmid. This
1536 # check is done by verifying the VNC ticket (inside VNC protocol).
1538 my $port = $param->{port
};
1540 return { port
=> $port };
1543 __PACKAGE__-
>register_method({
1544 name
=> 'spiceproxy',
1545 path
=> '{vmid}/spiceproxy',
1550 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1552 description
=> "Returns a SPICE configuration to connect to the VM.",
1554 additionalProperties
=> 0,
1556 node
=> get_standard_option
('pve-node'),
1557 vmid
=> get_standard_option
('pve-vmid'),
1558 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1561 returns
=> get_standard_option
('remote-viewer-config'),
1565 my $rpcenv = PVE
::RPCEnvironment
::get
();
1567 my $authuser = $rpcenv->get_user();
1569 my $vmid = $param->{vmid
};
1570 my $node = $param->{node
};
1571 my $proxy = $param->{proxy
};
1573 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1574 my $title = "VM $vmid";
1575 $title .= " - ". $conf->{name
} if $conf->{name
};
1577 my $port = PVE
::QemuServer
::spice_port
($vmid);
1579 my ($ticket, undef, $remote_viewer_config) =
1580 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1582 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1583 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1585 return $remote_viewer_config;
1588 __PACKAGE__-
>register_method({
1590 path
=> '{vmid}/status',
1593 description
=> "Directory index",
1598 additionalProperties
=> 0,
1600 node
=> get_standard_option
('pve-node'),
1601 vmid
=> get_standard_option
('pve-vmid'),
1609 subdir
=> { type
=> 'string' },
1612 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1618 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1621 { subdir
=> 'current' },
1622 { subdir
=> 'start' },
1623 { subdir
=> 'stop' },
1629 __PACKAGE__-
>register_method({
1630 name
=> 'vm_status',
1631 path
=> '{vmid}/status/current',
1634 protected
=> 1, # qemu pid files are only readable by root
1635 description
=> "Get virtual machine status.",
1637 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1640 additionalProperties
=> 0,
1642 node
=> get_standard_option
('pve-node'),
1643 vmid
=> get_standard_option
('pve-vmid'),
1646 returns
=> { type
=> 'object' },
1651 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1653 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1654 my $status = $vmstatus->{$param->{vmid
}};
1656 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1658 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1663 __PACKAGE__-
>register_method({
1665 path
=> '{vmid}/status/start',
1669 description
=> "Start virtual machine.",
1671 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1674 additionalProperties
=> 0,
1676 node
=> get_standard_option
('pve-node'),
1677 vmid
=> get_standard_option
('pve-vmid',
1678 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1679 skiplock
=> get_standard_option
('skiplock'),
1680 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1681 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1684 enum
=> ['secure', 'insecure'],
1685 description
=> "Migration traffic is encrypted using an SSH " .
1686 "tunnel by default. On secure, completely private networks " .
1687 "this can be disabled to increase performance.",
1690 migration_network
=> {
1691 type
=> 'string', format
=> 'CIDR',
1692 description
=> "CIDR of the (sub) network that is used for migration.",
1695 machine
=> get_standard_option
('pve-qm-machine'),
1697 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1709 my $rpcenv = PVE
::RPCEnvironment
::get
();
1711 my $authuser = $rpcenv->get_user();
1713 my $node = extract_param
($param, 'node');
1715 my $vmid = extract_param
($param, 'vmid');
1717 my $machine = extract_param
($param, 'machine');
1719 my $stateuri = extract_param
($param, 'stateuri');
1720 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1721 if $stateuri && $authuser ne 'root@pam';
1723 my $skiplock = extract_param
($param, 'skiplock');
1724 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1725 if $skiplock && $authuser ne 'root@pam';
1727 my $migratedfrom = extract_param
($param, 'migratedfrom');
1728 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1729 if $migratedfrom && $authuser ne 'root@pam';
1731 my $migration_type = extract_param
($param, 'migration_type');
1732 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1733 if $migration_type && $authuser ne 'root@pam';
1735 my $migration_network = extract_param
($param, 'migration_network');
1736 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1737 if $migration_network && $authuser ne 'root@pam';
1739 my $targetstorage = extract_param
($param, 'targetstorage');
1740 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1741 if $targetstorage && $authuser ne 'root@pam';
1743 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1744 if $targetstorage && !$migratedfrom;
1746 # read spice ticket from STDIN
1748 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1749 if (defined(my $line = <>)) {
1751 $spice_ticket = $line;
1755 PVE
::Cluster
::check_cfs_quorum
();
1757 my $storecfg = PVE
::Storage
::config
();
1759 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1760 $rpcenv->{type
} ne 'ha') {
1765 my $service = "vm:$vmid";
1767 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1769 print "Requesting HA start for VM $vmid\n";
1771 PVE
::Tools
::run_command
($cmd);
1776 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1783 syslog
('info', "start VM $vmid: $upid\n");
1785 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1786 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1791 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1795 __PACKAGE__-
>register_method({
1797 path
=> '{vmid}/status/stop',
1801 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1802 "is akin to pulling the power plug of a running computer and may damage the VM data",
1804 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1807 additionalProperties
=> 0,
1809 node
=> get_standard_option
('pve-node'),
1810 vmid
=> get_standard_option
('pve-vmid',
1811 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1812 skiplock
=> get_standard_option
('skiplock'),
1813 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1815 description
=> "Wait maximal timeout seconds.",
1821 description
=> "Do not deactivate storage volumes.",
1834 my $rpcenv = PVE
::RPCEnvironment
::get
();
1836 my $authuser = $rpcenv->get_user();
1838 my $node = extract_param
($param, 'node');
1840 my $vmid = extract_param
($param, 'vmid');
1842 my $skiplock = extract_param
($param, 'skiplock');
1843 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1844 if $skiplock && $authuser ne 'root@pam';
1846 my $keepActive = extract_param
($param, 'keepActive');
1847 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1848 if $keepActive && $authuser ne 'root@pam';
1850 my $migratedfrom = extract_param
($param, 'migratedfrom');
1851 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1852 if $migratedfrom && $authuser ne 'root@pam';
1855 my $storecfg = PVE
::Storage
::config
();
1857 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1862 my $service = "vm:$vmid";
1864 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1866 print "Requesting HA stop for VM $vmid\n";
1868 PVE
::Tools
::run_command
($cmd);
1873 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1879 syslog
('info', "stop VM $vmid: $upid\n");
1881 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1882 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1887 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1891 __PACKAGE__-
>register_method({
1893 path
=> '{vmid}/status/reset',
1897 description
=> "Reset virtual machine.",
1899 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1902 additionalProperties
=> 0,
1904 node
=> get_standard_option
('pve-node'),
1905 vmid
=> get_standard_option
('pve-vmid',
1906 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1907 skiplock
=> get_standard_option
('skiplock'),
1916 my $rpcenv = PVE
::RPCEnvironment
::get
();
1918 my $authuser = $rpcenv->get_user();
1920 my $node = extract_param
($param, 'node');
1922 my $vmid = extract_param
($param, 'vmid');
1924 my $skiplock = extract_param
($param, 'skiplock');
1925 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1926 if $skiplock && $authuser ne 'root@pam';
1928 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1933 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1938 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1941 __PACKAGE__-
>register_method({
1942 name
=> 'vm_shutdown',
1943 path
=> '{vmid}/status/shutdown',
1947 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1948 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1950 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1953 additionalProperties
=> 0,
1955 node
=> get_standard_option
('pve-node'),
1956 vmid
=> get_standard_option
('pve-vmid',
1957 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1958 skiplock
=> get_standard_option
('skiplock'),
1960 description
=> "Wait maximal timeout seconds.",
1966 description
=> "Make sure the VM stops.",
1972 description
=> "Do not deactivate storage volumes.",
1985 my $rpcenv = PVE
::RPCEnvironment
::get
();
1987 my $authuser = $rpcenv->get_user();
1989 my $node = extract_param
($param, 'node');
1991 my $vmid = extract_param
($param, 'vmid');
1993 my $skiplock = extract_param
($param, 'skiplock');
1994 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1995 if $skiplock && $authuser ne 'root@pam';
1997 my $keepActive = extract_param
($param, 'keepActive');
1998 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1999 if $keepActive && $authuser ne 'root@pam';
2001 my $storecfg = PVE
::Storage
::config
();
2005 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2006 # otherwise, we will infer a shutdown command, but run into the timeout,
2007 # then when the vm is resumed, it will instantly shutdown
2009 # checking the qmp status here to get feedback to the gui/cli/api
2010 # and the status query should not take too long
2013 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2017 if (!$err && $qmpstatus->{status
} eq "paused") {
2018 if ($param->{forceStop
}) {
2019 warn "VM is paused - stop instead of shutdown\n";
2022 die "VM is paused - cannot shutdown\n";
2026 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2027 ($rpcenv->{type
} ne 'ha')) {
2032 my $service = "vm:$vmid";
2034 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2036 print "Requesting HA stop for VM $vmid\n";
2038 PVE
::Tools
::run_command
($cmd);
2043 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2050 syslog
('info', "shutdown VM $vmid: $upid\n");
2052 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2053 $shutdown, $param->{forceStop
}, $keepActive);
2058 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2062 __PACKAGE__-
>register_method({
2063 name
=> 'vm_suspend',
2064 path
=> '{vmid}/status/suspend',
2068 description
=> "Suspend virtual machine.",
2070 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2073 additionalProperties
=> 0,
2075 node
=> get_standard_option
('pve-node'),
2076 vmid
=> get_standard_option
('pve-vmid',
2077 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2078 skiplock
=> get_standard_option
('skiplock'),
2087 my $rpcenv = PVE
::RPCEnvironment
::get
();
2089 my $authuser = $rpcenv->get_user();
2091 my $node = extract_param
($param, 'node');
2093 my $vmid = extract_param
($param, 'vmid');
2095 my $skiplock = extract_param
($param, 'skiplock');
2096 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2097 if $skiplock && $authuser ne 'root@pam';
2099 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2104 syslog
('info', "suspend VM $vmid: $upid\n");
2106 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2111 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2114 __PACKAGE__-
>register_method({
2115 name
=> 'vm_resume',
2116 path
=> '{vmid}/status/resume',
2120 description
=> "Resume virtual machine.",
2122 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2125 additionalProperties
=> 0,
2127 node
=> get_standard_option
('pve-node'),
2128 vmid
=> get_standard_option
('pve-vmid',
2129 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2130 skiplock
=> get_standard_option
('skiplock'),
2131 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2141 my $rpcenv = PVE
::RPCEnvironment
::get
();
2143 my $authuser = $rpcenv->get_user();
2145 my $node = extract_param
($param, 'node');
2147 my $vmid = extract_param
($param, 'vmid');
2149 my $skiplock = extract_param
($param, 'skiplock');
2150 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2151 if $skiplock && $authuser ne 'root@pam';
2153 my $nocheck = extract_param
($param, 'nocheck');
2155 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2160 syslog
('info', "resume VM $vmid: $upid\n");
2162 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2167 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2170 __PACKAGE__-
>register_method({
2171 name
=> 'vm_sendkey',
2172 path
=> '{vmid}/sendkey',
2176 description
=> "Send key event to virtual machine.",
2178 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2181 additionalProperties
=> 0,
2183 node
=> get_standard_option
('pve-node'),
2184 vmid
=> get_standard_option
('pve-vmid',
2185 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2186 skiplock
=> get_standard_option
('skiplock'),
2188 description
=> "The key (qemu monitor encoding).",
2193 returns
=> { type
=> 'null'},
2197 my $rpcenv = PVE
::RPCEnvironment
::get
();
2199 my $authuser = $rpcenv->get_user();
2201 my $node = extract_param
($param, 'node');
2203 my $vmid = extract_param
($param, 'vmid');
2205 my $skiplock = extract_param
($param, 'skiplock');
2206 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2207 if $skiplock && $authuser ne 'root@pam';
2209 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2214 __PACKAGE__-
>register_method({
2215 name
=> 'vm_feature',
2216 path
=> '{vmid}/feature',
2220 description
=> "Check if feature for virtual machine is available.",
2222 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2225 additionalProperties
=> 0,
2227 node
=> get_standard_option
('pve-node'),
2228 vmid
=> get_standard_option
('pve-vmid'),
2230 description
=> "Feature to check.",
2232 enum
=> [ 'snapshot', 'clone', 'copy' ],
2234 snapname
=> get_standard_option
('pve-snapshot-name', {
2242 hasFeature
=> { type
=> 'boolean' },
2245 items
=> { type
=> 'string' },
2252 my $node = extract_param
($param, 'node');
2254 my $vmid = extract_param
($param, 'vmid');
2256 my $snapname = extract_param
($param, 'snapname');
2258 my $feature = extract_param
($param, 'feature');
2260 my $running = PVE
::QemuServer
::check_running
($vmid);
2262 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2265 my $snap = $conf->{snapshots
}->{$snapname};
2266 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2269 my $storecfg = PVE
::Storage
::config
();
2271 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2272 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2275 hasFeature
=> $hasFeature,
2276 nodes
=> [ keys %$nodelist ],
2280 __PACKAGE__-
>register_method({
2282 path
=> '{vmid}/clone',
2286 description
=> "Create a copy of virtual machine/template.",
2288 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2289 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2290 "'Datastore.AllocateSpace' on any used storage.",
2293 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2295 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2296 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2301 additionalProperties
=> 0,
2303 node
=> get_standard_option
('pve-node'),
2304 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2305 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2308 type
=> 'string', format
=> 'dns-name',
2309 description
=> "Set a name for the new VM.",
2314 description
=> "Description for the new VM.",
2318 type
=> 'string', format
=> 'pve-poolid',
2319 description
=> "Add the new VM to the specified pool.",
2321 snapname
=> get_standard_option
('pve-snapshot-name', {
2324 storage
=> get_standard_option
('pve-storage-id', {
2325 description
=> "Target storage for full clone.",
2330 description
=> "Target format for file storage.",
2334 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2339 description
=> "Create a full copy of all disk. This is always done when " .
2340 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2343 target
=> get_standard_option
('pve-node', {
2344 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2355 my $rpcenv = PVE
::RPCEnvironment
::get
();
2357 my $authuser = $rpcenv->get_user();
2359 my $node = extract_param
($param, 'node');
2361 my $vmid = extract_param
($param, 'vmid');
2363 my $newid = extract_param
($param, 'newid');
2365 my $pool = extract_param
($param, 'pool');
2367 if (defined($pool)) {
2368 $rpcenv->check_pool_exist($pool);
2371 my $snapname = extract_param
($param, 'snapname');
2373 my $storage = extract_param
($param, 'storage');
2375 my $format = extract_param
($param, 'format');
2377 my $target = extract_param
($param, 'target');
2379 my $localnode = PVE
::INotify
::nodename
();
2381 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2383 PVE
::Cluster
::check_node_exists
($target) if $target;
2385 my $storecfg = PVE
::Storage
::config
();
2388 # check if storage is enabled on local node
2389 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2391 # check if storage is available on target node
2392 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2393 # clone only works if target storage is shared
2394 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2395 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2399 PVE
::Cluster
::check_cfs_quorum
();
2401 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2403 # exclusive lock if VM is running - else shared lock is enough;
2404 my $shared_lock = $running ?
0 : 1;
2408 # do all tests after lock
2409 # we also try to do all tests before we fork the worker
2411 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2413 PVE
::QemuConfig-
>check_lock($conf);
2415 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2417 die "unexpected state change\n" if $verify_running != $running;
2419 die "snapshot '$snapname' does not exist\n"
2420 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2422 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2424 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2426 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2428 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2430 die "unable to create VM $newid: config file already exists\n"
2433 my $newconf = { lock => 'clone' };
2438 foreach my $opt (keys %$oldconf) {
2439 my $value = $oldconf->{$opt};
2441 # do not copy snapshot related info
2442 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2443 $opt eq 'vmstate' || $opt eq 'snapstate';
2445 # no need to copy unused images, because VMID(owner) changes anyways
2446 next if $opt =~ m/^unused\d+$/;
2448 # always change MAC! address
2449 if ($opt =~ m/^net(\d+)$/) {
2450 my $net = PVE
::QemuServer
::parse_net
($value);
2451 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2452 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2453 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2454 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2455 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2456 die "unable to parse drive options for '$opt'\n" if !$drive;
2457 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2458 $newconf->{$opt} = $value; # simply copy configuration
2460 if ($param->{full
}) {
2461 die "Full clone feature is not supported for drive '$opt'\n"
2462 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2463 $fullclone->{$opt} = 1;
2465 # not full means clone instead of copy
2466 die "Linked clone feature is not supported for drive '$opt'\n"
2467 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2469 $drives->{$opt} = $drive;
2470 push @$vollist, $drive->{file
};
2473 # copy everything else
2474 $newconf->{$opt} = $value;
2478 # auto generate a new uuid
2479 my ($uuid, $uuid_str);
2480 UUID
::generate
($uuid);
2481 UUID
::unparse
($uuid, $uuid_str);
2482 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2483 $smbios1->{uuid
} = $uuid_str;
2484 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2486 delete $newconf->{template
};
2488 if ($param->{name
}) {
2489 $newconf->{name
} = $param->{name
};
2491 if ($oldconf->{name
}) {
2492 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2494 $newconf->{name
} = "Copy-of-VM-$vmid";
2498 if ($param->{description
}) {
2499 $newconf->{description
} = $param->{description
};
2502 # create empty/temp config - this fails if VM already exists on other node
2503 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2508 my $newvollist = [];
2515 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2517 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2519 my $total_jobs = scalar(keys %{$drives});
2522 foreach my $opt (keys %$drives) {
2523 my $drive = $drives->{$opt};
2524 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2526 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2527 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2528 $jobs, $skipcomplete, $oldconf->{agent
});
2530 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2532 PVE
::QemuConfig-
>write_config($newid, $newconf);
2536 delete $newconf->{lock};
2537 PVE
::QemuConfig-
>write_config($newid, $newconf);
2540 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2541 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2542 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2544 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2545 die "Failed to move config to node '$target' - rename failed: $!\n"
2546 if !rename($conffile, $newconffile);
2549 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2554 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2556 sleep 1; # some storage like rbd need to wait before release volume - really?
2558 foreach my $volid (@$newvollist) {
2559 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2562 die "clone failed: $err";
2568 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2570 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2573 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2574 # Aquire exclusive lock lock for $newid
2575 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2580 __PACKAGE__-
>register_method({
2581 name
=> 'move_vm_disk',
2582 path
=> '{vmid}/move_disk',
2586 description
=> "Move volume to different storage.",
2588 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2590 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2591 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2595 additionalProperties
=> 0,
2597 node
=> get_standard_option
('pve-node'),
2598 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2601 description
=> "The disk you want to move.",
2602 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2604 storage
=> get_standard_option
('pve-storage-id', {
2605 description
=> "Target storage.",
2606 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2610 description
=> "Target Format.",
2611 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2616 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2622 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2630 description
=> "the task ID.",
2635 my $rpcenv = PVE
::RPCEnvironment
::get
();
2637 my $authuser = $rpcenv->get_user();
2639 my $node = extract_param
($param, 'node');
2641 my $vmid = extract_param
($param, 'vmid');
2643 my $digest = extract_param
($param, 'digest');
2645 my $disk = extract_param
($param, 'disk');
2647 my $storeid = extract_param
($param, 'storage');
2649 my $format = extract_param
($param, 'format');
2651 my $storecfg = PVE
::Storage
::config
();
2653 my $updatefn = sub {
2655 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2657 PVE
::QemuConfig-
>check_lock($conf);
2659 die "checksum missmatch (file change by other user?)\n"
2660 if $digest && $digest ne $conf->{digest
};
2662 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2664 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2666 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2668 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2671 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2672 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2676 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2677 (!$format || !$oldfmt || $oldfmt eq $format);
2679 # this only checks snapshots because $disk is passed!
2680 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2681 die "you can't move a disk with snapshots and delete the source\n"
2682 if $snapshotted && $param->{delete};
2684 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2686 my $running = PVE
::QemuServer
::check_running
($vmid);
2688 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2692 my $newvollist = [];
2698 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2700 warn "moving disk with snapshots, snapshots will not be moved!\n"
2703 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2704 $vmid, $storeid, $format, 1, $newvollist);
2706 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2708 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2710 # convert moved disk to base if part of template
2711 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2712 if PVE
::QemuConfig-
>is_template($conf);
2714 PVE
::QemuConfig-
>write_config($vmid, $conf);
2717 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2718 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2725 foreach my $volid (@$newvollist) {
2726 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2729 die "storage migration failed: $err";
2732 if ($param->{delete}) {
2734 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2735 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2741 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2744 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2747 __PACKAGE__-
>register_method({
2748 name
=> 'migrate_vm',
2749 path
=> '{vmid}/migrate',
2753 description
=> "Migrate virtual machine. Creates a new migration task.",
2755 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2758 additionalProperties
=> 0,
2760 node
=> get_standard_option
('pve-node'),
2761 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2762 target
=> get_standard_option
('pve-node', {
2763 description
=> "Target node.",
2764 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2768 description
=> "Use online/live migration.",
2773 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2778 enum
=> ['secure', 'insecure'],
2779 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2782 migration_network
=> {
2783 type
=> 'string', format
=> 'CIDR',
2784 description
=> "CIDR of the (sub) network that is used for migration.",
2787 "with-local-disks" => {
2789 description
=> "Enable live storage migration for local disk",
2792 targetstorage
=> get_standard_option
('pve-storage-id', {
2793 description
=> "Default target storage.",
2795 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2801 description
=> "the task ID.",
2806 my $rpcenv = PVE
::RPCEnvironment
::get
();
2808 my $authuser = $rpcenv->get_user();
2810 my $target = extract_param
($param, 'target');
2812 my $localnode = PVE
::INotify
::nodename
();
2813 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2815 PVE
::Cluster
::check_cfs_quorum
();
2817 PVE
::Cluster
::check_node_exists
($target);
2819 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2821 my $vmid = extract_param
($param, 'vmid');
2823 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2824 if !$param->{online
} && $param->{targetstorage
};
2826 raise_param_exc
({ force
=> "Only root may use this option." })
2827 if $param->{force
} && $authuser ne 'root@pam';
2829 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2830 if $param->{migration_type
} && $authuser ne 'root@pam';
2832 # allow root only until better network permissions are available
2833 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2834 if $param->{migration_network
} && $authuser ne 'root@pam';
2837 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2839 # try to detect errors early
2841 PVE
::QemuConfig-
>check_lock($conf);
2843 if (PVE
::QemuServer
::check_running
($vmid)) {
2844 die "cant migrate running VM without --online\n"
2845 if !$param->{online
};
2848 my $storecfg = PVE
::Storage
::config
();
2850 if( $param->{targetstorage
}) {
2851 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2853 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2856 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2861 my $service = "vm:$vmid";
2863 my $cmd = ['ha-manager', 'migrate', $service, $target];
2865 print "Requesting HA migration for VM $vmid to node $target\n";
2867 PVE
::Tools
::run_command
($cmd);
2872 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2877 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2881 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2884 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2889 __PACKAGE__-
>register_method({
2891 path
=> '{vmid}/monitor',
2895 description
=> "Execute Qemu monitor commands.",
2897 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2898 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2901 additionalProperties
=> 0,
2903 node
=> get_standard_option
('pve-node'),
2904 vmid
=> get_standard_option
('pve-vmid'),
2907 description
=> "The monitor command.",
2911 returns
=> { type
=> 'string'},
2915 my $rpcenv = PVE
::RPCEnvironment
::get
();
2916 my $authuser = $rpcenv->get_user();
2919 my $command = shift;
2920 return $command =~ m/^\s*info(\s+|$)/
2921 || $command =~ m/^\s*help\s*$/;
2924 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2925 if !&$is_ro($param->{command
});
2927 my $vmid = $param->{vmid
};
2929 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2933 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2935 $res = "ERROR: $@" if $@;
2940 my $guest_agent_commands = [
2948 'network-get-interfaces',
2951 'get-memory-blocks',
2952 'get-memory-block-info',
2959 __PACKAGE__-
>register_method({
2961 path
=> '{vmid}/agent',
2965 description
=> "Execute Qemu Guest Agent commands.",
2967 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2970 additionalProperties
=> 0,
2972 node
=> get_standard_option
('pve-node'),
2973 vmid
=> get_standard_option
('pve-vmid', {
2974 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2977 description
=> "The QGA command.",
2978 enum
=> $guest_agent_commands,
2984 description
=> "Returns an object with a single `result` property. The type of that
2985 property depends on the executed command.",
2990 my $vmid = $param->{vmid
};
2992 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2994 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2995 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2997 my $cmd = $param->{command
};
2999 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3001 return { result
=> $res };
3004 __PACKAGE__-
>register_method({
3005 name
=> 'resize_vm',
3006 path
=> '{vmid}/resize',
3010 description
=> "Extend volume size.",
3012 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3015 additionalProperties
=> 0,
3017 node
=> get_standard_option
('pve-node'),
3018 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3019 skiplock
=> get_standard_option
('skiplock'),
3022 description
=> "The disk you want to resize.",
3023 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3027 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3028 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.",
3032 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3038 returns
=> { type
=> 'null'},
3042 my $rpcenv = PVE
::RPCEnvironment
::get
();
3044 my $authuser = $rpcenv->get_user();
3046 my $node = extract_param
($param, 'node');
3048 my $vmid = extract_param
($param, 'vmid');
3050 my $digest = extract_param
($param, 'digest');
3052 my $disk = extract_param
($param, 'disk');
3054 my $sizestr = extract_param
($param, 'size');
3056 my $skiplock = extract_param
($param, 'skiplock');
3057 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3058 if $skiplock && $authuser ne 'root@pam';
3060 my $storecfg = PVE
::Storage
::config
();
3062 my $updatefn = sub {
3064 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3066 die "checksum missmatch (file change by other user?)\n"
3067 if $digest && $digest ne $conf->{digest
};
3068 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3070 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3072 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3074 my (undef, undef, undef, undef, undef, undef, $format) =
3075 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3077 die "can't resize volume: $disk if snapshot exists\n"
3078 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3080 my $volid = $drive->{file
};
3082 die "disk '$disk' has no associated volume\n" if !$volid;
3084 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3086 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3088 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3090 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3091 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3093 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3094 my ($ext, $newsize, $unit) = ($1, $2, $4);
3097 $newsize = $newsize * 1024;
3098 } elsif ($unit eq 'M') {
3099 $newsize = $newsize * 1024 * 1024;
3100 } elsif ($unit eq 'G') {
3101 $newsize = $newsize * 1024 * 1024 * 1024;
3102 } elsif ($unit eq 'T') {
3103 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3106 $newsize += $size if $ext;
3107 $newsize = int($newsize);
3109 die "shrinking disks is not supported\n" if $newsize < $size;
3111 return if $size == $newsize;
3113 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3115 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3117 $drive->{size
} = $newsize;
3118 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3120 PVE
::QemuConfig-
>write_config($vmid, $conf);
3123 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3127 __PACKAGE__-
>register_method({
3128 name
=> 'snapshot_list',
3129 path
=> '{vmid}/snapshot',
3131 description
=> "List all snapshots.",
3133 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3136 protected
=> 1, # qemu pid files are only readable by root
3138 additionalProperties
=> 0,
3140 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3141 node
=> get_standard_option
('pve-node'),
3150 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3155 my $vmid = $param->{vmid
};
3157 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3158 my $snaphash = $conf->{snapshots
} || {};
3162 foreach my $name (keys %$snaphash) {
3163 my $d = $snaphash->{$name};
3166 snaptime
=> $d->{snaptime
} || 0,
3167 vmstate
=> $d->{vmstate
} ?
1 : 0,
3168 description
=> $d->{description
} || '',
3170 $item->{parent
} = $d->{parent
} if $d->{parent
};
3171 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3175 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3176 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3177 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3179 push @$res, $current;
3184 __PACKAGE__-
>register_method({
3186 path
=> '{vmid}/snapshot',
3190 description
=> "Snapshot a VM.",
3192 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3195 additionalProperties
=> 0,
3197 node
=> get_standard_option
('pve-node'),
3198 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3199 snapname
=> get_standard_option
('pve-snapshot-name'),
3203 description
=> "Save the vmstate",
3208 description
=> "A textual description or comment.",
3214 description
=> "the task ID.",
3219 my $rpcenv = PVE
::RPCEnvironment
::get
();
3221 my $authuser = $rpcenv->get_user();
3223 my $node = extract_param
($param, 'node');
3225 my $vmid = extract_param
($param, 'vmid');
3227 my $snapname = extract_param
($param, 'snapname');
3229 die "unable to use snapshot name 'current' (reserved name)\n"
3230 if $snapname eq 'current';
3233 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3234 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3235 $param->{description
});
3238 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3241 __PACKAGE__-
>register_method({
3242 name
=> 'snapshot_cmd_idx',
3243 path
=> '{vmid}/snapshot/{snapname}',
3250 additionalProperties
=> 0,
3252 vmid
=> get_standard_option
('pve-vmid'),
3253 node
=> get_standard_option
('pve-node'),
3254 snapname
=> get_standard_option
('pve-snapshot-name'),
3263 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3270 push @$res, { cmd
=> 'rollback' };
3271 push @$res, { cmd
=> 'config' };
3276 __PACKAGE__-
>register_method({
3277 name
=> 'update_snapshot_config',
3278 path
=> '{vmid}/snapshot/{snapname}/config',
3282 description
=> "Update snapshot metadata.",
3284 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3287 additionalProperties
=> 0,
3289 node
=> get_standard_option
('pve-node'),
3290 vmid
=> get_standard_option
('pve-vmid'),
3291 snapname
=> get_standard_option
('pve-snapshot-name'),
3295 description
=> "A textual description or comment.",
3299 returns
=> { type
=> 'null' },
3303 my $rpcenv = PVE
::RPCEnvironment
::get
();
3305 my $authuser = $rpcenv->get_user();
3307 my $vmid = extract_param
($param, 'vmid');
3309 my $snapname = extract_param
($param, 'snapname');
3311 return undef if !defined($param->{description
});
3313 my $updatefn = sub {
3315 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3317 PVE
::QemuConfig-
>check_lock($conf);
3319 my $snap = $conf->{snapshots
}->{$snapname};
3321 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3323 $snap->{description
} = $param->{description
} if defined($param->{description
});
3325 PVE
::QemuConfig-
>write_config($vmid, $conf);
3328 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3333 __PACKAGE__-
>register_method({
3334 name
=> 'get_snapshot_config',
3335 path
=> '{vmid}/snapshot/{snapname}/config',
3338 description
=> "Get snapshot configuration",
3340 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3343 additionalProperties
=> 0,
3345 node
=> get_standard_option
('pve-node'),
3346 vmid
=> get_standard_option
('pve-vmid'),
3347 snapname
=> get_standard_option
('pve-snapshot-name'),
3350 returns
=> { type
=> "object" },
3354 my $rpcenv = PVE
::RPCEnvironment
::get
();
3356 my $authuser = $rpcenv->get_user();
3358 my $vmid = extract_param
($param, 'vmid');
3360 my $snapname = extract_param
($param, 'snapname');
3362 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3364 my $snap = $conf->{snapshots
}->{$snapname};
3366 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3371 __PACKAGE__-
>register_method({
3373 path
=> '{vmid}/snapshot/{snapname}/rollback',
3377 description
=> "Rollback VM state to specified snapshot.",
3379 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3382 additionalProperties
=> 0,
3384 node
=> get_standard_option
('pve-node'),
3385 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3386 snapname
=> get_standard_option
('pve-snapshot-name'),
3391 description
=> "the task ID.",
3396 my $rpcenv = PVE
::RPCEnvironment
::get
();
3398 my $authuser = $rpcenv->get_user();
3400 my $node = extract_param
($param, 'node');
3402 my $vmid = extract_param
($param, 'vmid');
3404 my $snapname = extract_param
($param, 'snapname');
3407 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3408 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3412 # hold migration lock, this makes sure that nobody create replication snapshots
3413 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3416 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3419 __PACKAGE__-
>register_method({
3420 name
=> 'delsnapshot',
3421 path
=> '{vmid}/snapshot/{snapname}',
3425 description
=> "Delete a VM snapshot.",
3427 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3430 additionalProperties
=> 0,
3432 node
=> get_standard_option
('pve-node'),
3433 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3434 snapname
=> get_standard_option
('pve-snapshot-name'),
3438 description
=> "For removal from config file, even if removing disk snapshots fails.",
3444 description
=> "the task ID.",
3449 my $rpcenv = PVE
::RPCEnvironment
::get
();
3451 my $authuser = $rpcenv->get_user();
3453 my $node = extract_param
($param, 'node');
3455 my $vmid = extract_param
($param, 'vmid');
3457 my $snapname = extract_param
($param, 'snapname');
3460 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3461 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3464 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3467 __PACKAGE__-
>register_method({
3469 path
=> '{vmid}/template',
3473 description
=> "Create a Template.",
3475 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3476 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3479 additionalProperties
=> 0,
3481 node
=> get_standard_option
('pve-node'),
3482 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3486 description
=> "If you want to convert only 1 disk to base image.",
3487 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3492 returns
=> { type
=> 'null'},
3496 my $rpcenv = PVE
::RPCEnvironment
::get
();
3498 my $authuser = $rpcenv->get_user();
3500 my $node = extract_param
($param, 'node');
3502 my $vmid = extract_param
($param, 'vmid');
3504 my $disk = extract_param
($param, 'disk');
3506 my $updatefn = sub {
3508 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3510 PVE
::QemuConfig-
>check_lock($conf);
3512 die "unable to create template, because VM contains snapshots\n"
3513 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3515 die "you can't convert a template to a template\n"
3516 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3518 die "you can't convert a VM to template if VM is running\n"
3519 if PVE
::QemuServer
::check_running
($vmid);
3522 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3525 $conf->{template
} = 1;
3526 PVE
::QemuConfig-
>write_config($vmid, $conf);
3528 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3531 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);