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 # ensure no old replication state are exists
530 PVE
::ReplicationState
::delete_guest_states
($vmid);
532 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
538 PVE
::Cluster
::check_vmid_unused
($vmid);
540 # ensure no old replication state are exists
541 PVE
::ReplicationState
::delete_guest_states
($vmid);
551 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
553 # try to be smart about bootdisk
554 my @disks = PVE
::QemuServer
::valid_drive_names
();
556 foreach my $ds (reverse @disks) {
557 next if !$conf->{$ds};
558 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
559 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
563 if (!$conf->{bootdisk
} && $firstdisk) {
564 $conf->{bootdisk
} = $firstdisk;
567 # auto generate uuid if user did not specify smbios1 option
568 if (!$conf->{smbios1
}) {
569 my ($uuid, $uuid_str);
570 UUID
::generate
($uuid);
571 UUID
::unparse
($uuid, $uuid_str);
572 $conf->{smbios1
} = "uuid=$uuid_str";
575 PVE
::QemuConfig-
>write_config($vmid, $conf);
581 foreach my $volid (@$vollist) {
582 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
585 die "create failed - $err";
588 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
591 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
594 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
597 __PACKAGE__-
>register_method({
602 description
=> "Directory index",
607 additionalProperties
=> 0,
609 node
=> get_standard_option
('pve-node'),
610 vmid
=> get_standard_option
('pve-vmid'),
618 subdir
=> { type
=> 'string' },
621 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
627 { subdir
=> 'config' },
628 { subdir
=> 'pending' },
629 { subdir
=> 'status' },
630 { subdir
=> 'unlink' },
631 { subdir
=> 'vncproxy' },
632 { subdir
=> 'migrate' },
633 { subdir
=> 'resize' },
634 { subdir
=> 'move' },
636 { subdir
=> 'rrddata' },
637 { subdir
=> 'monitor' },
638 { subdir
=> 'agent' },
639 { subdir
=> 'snapshot' },
640 { subdir
=> 'spiceproxy' },
641 { subdir
=> 'sendkey' },
642 { subdir
=> 'firewall' },
648 __PACKAGE__-
>register_method ({
649 subclass
=> "PVE::API2::Firewall::VM",
650 path
=> '{vmid}/firewall',
653 __PACKAGE__-
>register_method({
655 path
=> '{vmid}/rrd',
657 protected
=> 1, # fixme: can we avoid that?
659 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
661 description
=> "Read VM RRD statistics (returns PNG)",
663 additionalProperties
=> 0,
665 node
=> get_standard_option
('pve-node'),
666 vmid
=> get_standard_option
('pve-vmid'),
668 description
=> "Specify the time frame you are interested in.",
670 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
673 description
=> "The list of datasources you want to display.",
674 type
=> 'string', format
=> 'pve-configid-list',
677 description
=> "The RRD consolidation function",
679 enum
=> [ 'AVERAGE', 'MAX' ],
687 filename
=> { type
=> 'string' },
693 return PVE
::Cluster
::create_rrd_graph
(
694 "pve2-vm/$param->{vmid}", $param->{timeframe
},
695 $param->{ds
}, $param->{cf
});
699 __PACKAGE__-
>register_method({
701 path
=> '{vmid}/rrddata',
703 protected
=> 1, # fixme: can we avoid that?
705 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
707 description
=> "Read VM RRD statistics",
709 additionalProperties
=> 0,
711 node
=> get_standard_option
('pve-node'),
712 vmid
=> get_standard_option
('pve-vmid'),
714 description
=> "Specify the time frame you are interested in.",
716 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
719 description
=> "The RRD consolidation function",
721 enum
=> [ 'AVERAGE', 'MAX' ],
736 return PVE
::Cluster
::create_rrd_data
(
737 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
741 __PACKAGE__-
>register_method({
743 path
=> '{vmid}/config',
746 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
748 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
751 additionalProperties
=> 0,
753 node
=> get_standard_option
('pve-node'),
754 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
756 description
=> "Get current values (instead of pending values).",
768 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
775 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
777 delete $conf->{snapshots
};
779 if (!$param->{current
}) {
780 foreach my $opt (keys %{$conf->{pending
}}) {
781 next if $opt eq 'delete';
782 my $value = $conf->{pending
}->{$opt};
783 next if ref($value); # just to be sure
784 $conf->{$opt} = $value;
786 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
787 foreach my $opt (keys %$pending_delete_hash) {
788 delete $conf->{$opt} if $conf->{$opt};
792 delete $conf->{pending
};
797 __PACKAGE__-
>register_method({
798 name
=> 'vm_pending',
799 path
=> '{vmid}/pending',
802 description
=> "Get virtual machine configuration, including pending changes.",
804 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
807 additionalProperties
=> 0,
809 node
=> get_standard_option
('pve-node'),
810 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
819 description
=> "Configuration option name.",
823 description
=> "Current value.",
828 description
=> "Pending value.",
833 description
=> "Indicates a pending delete request if present and not 0. " .
834 "The value 2 indicates a force-delete request.",
846 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
848 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
852 foreach my $opt (keys %$conf) {
853 next if ref($conf->{$opt});
854 my $item = { key
=> $opt };
855 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
856 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
857 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
861 foreach my $opt (keys %{$conf->{pending
}}) {
862 next if $opt eq 'delete';
863 next if ref($conf->{pending
}->{$opt}); # just to be sure
864 next if defined($conf->{$opt});
865 my $item = { key
=> $opt };
866 $item->{pending
} = $conf->{pending
}->{$opt};
870 while (my ($opt, $force) = each %$pending_delete_hash) {
871 next if $conf->{pending
}->{$opt}; # just to be sure
872 next if $conf->{$opt};
873 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
880 # POST/PUT {vmid}/config implementation
882 # The original API used PUT (idempotent) an we assumed that all operations
883 # are fast. But it turned out that almost any configuration change can
884 # involve hot-plug actions, or disk alloc/free. Such actions can take long
885 # time to complete and have side effects (not idempotent).
887 # The new implementation uses POST and forks a worker process. We added
888 # a new option 'background_delay'. If specified we wait up to
889 # 'background_delay' second for the worker task to complete. It returns null
890 # if the task is finished within that time, else we return the UPID.
892 my $update_vm_api = sub {
893 my ($param, $sync) = @_;
895 my $rpcenv = PVE
::RPCEnvironment
::get
();
897 my $authuser = $rpcenv->get_user();
899 my $node = extract_param
($param, 'node');
901 my $vmid = extract_param
($param, 'vmid');
903 my $digest = extract_param
($param, 'digest');
905 my $background_delay = extract_param
($param, 'background_delay');
907 my @paramarr = (); # used for log message
908 foreach my $key (keys %$param) {
909 push @paramarr, "-$key", $param->{$key};
912 my $skiplock = extract_param
($param, 'skiplock');
913 raise_param_exc
({ skiplock
=> "Only root may use this option." })
914 if $skiplock && $authuser ne 'root@pam';
916 my $delete_str = extract_param
($param, 'delete');
918 my $revert_str = extract_param
($param, 'revert');
920 my $force = extract_param
($param, 'force');
922 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
924 my $storecfg = PVE
::Storage
::config
();
926 my $defaults = PVE
::QemuServer
::load_defaults
();
928 &$resolve_cdrom_alias($param);
930 # now try to verify all parameters
933 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
934 if (!PVE
::QemuServer
::option_exists
($opt)) {
935 raise_param_exc
({ revert
=> "unknown option '$opt'" });
938 raise_param_exc
({ delete => "you can't use '-$opt' and " .
939 "-revert $opt' at the same time" })
940 if defined($param->{$opt});
946 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
947 $opt = 'ide2' if $opt eq 'cdrom';
949 raise_param_exc
({ delete => "you can't use '-$opt' and " .
950 "-delete $opt' at the same time" })
951 if defined($param->{$opt});
953 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
954 "-revert $opt' at the same time" })
957 if (!PVE
::QemuServer
::option_exists
($opt)) {
958 raise_param_exc
({ delete => "unknown option '$opt'" });
964 my $repl_conf = PVE
::ReplicationConfig-
>new();
965 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
966 my $check_replication = sub {
968 return if !$is_replicated;
969 my $volid = $drive->{file
};
970 return if !$volid || !($drive->{replicate
}//1);
971 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
972 my ($storeid, $format);
973 if ($volid =~ $NEW_DISK_RE) {
975 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
977 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
978 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
980 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
981 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
982 return if $scfg->{shared
};
983 die "cannot add non-replicatable volume to a replicated VM\n";
986 foreach my $opt (keys %$param) {
987 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
989 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
990 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
991 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
992 $check_replication->($drive);
993 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
994 } elsif ($opt =~ m/^net(\d+)$/) {
996 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
997 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1001 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1003 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1005 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1007 my $updatefn = sub {
1009 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1011 die "checksum missmatch (file change by other user?)\n"
1012 if $digest && $digest ne $conf->{digest
};
1014 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1016 foreach my $opt (keys %$revert) {
1017 if (defined($conf->{$opt})) {
1018 $param->{$opt} = $conf->{$opt};
1019 } elsif (defined($conf->{pending
}->{$opt})) {
1024 if ($param->{memory
} || defined($param->{balloon
})) {
1025 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1026 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1028 die "balloon value too large (must be smaller than assigned memory)\n"
1029 if $balloon && $balloon > $maxmem;
1032 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1036 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1038 # write updates to pending section
1040 my $modified = {}; # record what $option we modify
1042 foreach my $opt (@delete) {
1043 $modified->{$opt} = 1;
1044 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1045 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1046 warn "cannot delete '$opt' - not set in current configuration!\n";
1047 $modified->{$opt} = 0;
1051 if ($opt =~ m/^unused/) {
1052 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1053 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1054 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1055 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1056 delete $conf->{$opt};
1057 PVE
::QemuConfig-
>write_config($vmid, $conf);
1059 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1060 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1061 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1062 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1063 if defined($conf->{pending
}->{$opt});
1064 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1065 PVE
::QemuConfig-
>write_config($vmid, $conf);
1067 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1068 PVE
::QemuConfig-
>write_config($vmid, $conf);
1072 foreach my $opt (keys %$param) { # add/change
1073 $modified->{$opt} = 1;
1074 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1075 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1077 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1078 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1079 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1080 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1082 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1084 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1085 if defined($conf->{pending
}->{$opt});
1087 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1089 $conf->{pending
}->{$opt} = $param->{$opt};
1091 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1092 PVE
::QemuConfig-
>write_config($vmid, $conf);
1095 # remove pending changes when nothing changed
1096 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1097 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1098 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1100 return if !scalar(keys %{$conf->{pending
}});
1102 my $running = PVE
::QemuServer
::check_running
($vmid);
1104 # apply pending changes
1106 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1110 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1111 raise_param_exc
($errors) if scalar(keys %$errors);
1113 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1123 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1125 if ($background_delay) {
1127 # Note: It would be better to do that in the Event based HTTPServer
1128 # to avoid blocking call to sleep.
1130 my $end_time = time() + $background_delay;
1132 my $task = PVE
::Tools
::upid_decode
($upid);
1135 while (time() < $end_time) {
1136 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1138 sleep(1); # this gets interrupted when child process ends
1142 my $status = PVE
::Tools
::upid_read_status
($upid);
1143 return undef if $status eq 'OK';
1152 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1155 my $vm_config_perm_list = [
1160 'VM.Config.Network',
1162 'VM.Config.Options',
1165 __PACKAGE__-
>register_method({
1166 name
=> 'update_vm_async',
1167 path
=> '{vmid}/config',
1171 description
=> "Set virtual machine options (asynchrounous API).",
1173 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1176 additionalProperties
=> 0,
1177 properties
=> PVE
::QemuServer
::json_config_properties
(
1179 node
=> get_standard_option
('pve-node'),
1180 vmid
=> get_standard_option
('pve-vmid'),
1181 skiplock
=> get_standard_option
('skiplock'),
1183 type
=> 'string', format
=> 'pve-configid-list',
1184 description
=> "A list of settings you want to delete.",
1188 type
=> 'string', format
=> 'pve-configid-list',
1189 description
=> "Revert a pending change.",
1194 description
=> $opt_force_description,
1196 requires
=> 'delete',
1200 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1204 background_delay
=> {
1206 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1217 code
=> $update_vm_api,
1220 __PACKAGE__-
>register_method({
1221 name
=> 'update_vm',
1222 path
=> '{vmid}/config',
1226 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1228 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1231 additionalProperties
=> 0,
1232 properties
=> PVE
::QemuServer
::json_config_properties
(
1234 node
=> get_standard_option
('pve-node'),
1235 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1236 skiplock
=> get_standard_option
('skiplock'),
1238 type
=> 'string', format
=> 'pve-configid-list',
1239 description
=> "A list of settings you want to delete.",
1243 type
=> 'string', format
=> 'pve-configid-list',
1244 description
=> "Revert a pending change.",
1249 description
=> $opt_force_description,
1251 requires
=> 'delete',
1255 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1261 returns
=> { type
=> 'null' },
1264 &$update_vm_api($param, 1);
1270 __PACKAGE__-
>register_method({
1271 name
=> 'destroy_vm',
1276 description
=> "Destroy the vm (also delete all used/owned volumes).",
1278 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1281 additionalProperties
=> 0,
1283 node
=> get_standard_option
('pve-node'),
1284 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1285 skiplock
=> get_standard_option
('skiplock'),
1294 my $rpcenv = PVE
::RPCEnvironment
::get
();
1296 my $authuser = $rpcenv->get_user();
1298 my $vmid = $param->{vmid
};
1300 my $skiplock = $param->{skiplock
};
1301 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1302 if $skiplock && $authuser ne 'root@pam';
1305 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1307 my $storecfg = PVE
::Storage
::config
();
1309 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1311 die "unable to remove VM $vmid - used in HA resources\n"
1312 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1314 # do not allow destroy if there are replication jobs
1315 my $repl_conf = PVE
::ReplicationConfig-
>new();
1316 $repl_conf->check_for_existing_jobs($vmid);
1318 # early tests (repeat after locking)
1319 die "VM $vmid is running - destroy failed\n"
1320 if PVE
::QemuServer
::check_running
($vmid);
1325 syslog
('info', "destroy VM $vmid: $upid\n");
1327 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1329 PVE
::AccessControl
::remove_vm_access
($vmid);
1331 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1334 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1337 __PACKAGE__-
>register_method({
1339 path
=> '{vmid}/unlink',
1343 description
=> "Unlink/delete disk images.",
1345 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1348 additionalProperties
=> 0,
1350 node
=> get_standard_option
('pve-node'),
1351 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1353 type
=> 'string', format
=> 'pve-configid-list',
1354 description
=> "A list of disk IDs you want to delete.",
1358 description
=> $opt_force_description,
1363 returns
=> { type
=> 'null'},
1367 $param->{delete} = extract_param
($param, 'idlist');
1369 __PACKAGE__-
>update_vm($param);
1376 __PACKAGE__-
>register_method({
1378 path
=> '{vmid}/vncproxy',
1382 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1384 description
=> "Creates a TCP VNC proxy connections.",
1386 additionalProperties
=> 0,
1388 node
=> get_standard_option
('pve-node'),
1389 vmid
=> get_standard_option
('pve-vmid'),
1393 description
=> "starts websockify instead of vncproxy",
1398 additionalProperties
=> 0,
1400 user
=> { type
=> 'string' },
1401 ticket
=> { type
=> 'string' },
1402 cert
=> { type
=> 'string' },
1403 port
=> { type
=> 'integer' },
1404 upid
=> { type
=> 'string' },
1410 my $rpcenv = PVE
::RPCEnvironment
::get
();
1412 my $authuser = $rpcenv->get_user();
1414 my $vmid = $param->{vmid
};
1415 my $node = $param->{node
};
1416 my $websocket = $param->{websocket
};
1418 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1420 my $authpath = "/vms/$vmid";
1422 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1424 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1427 my ($remip, $family);
1430 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1431 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1432 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1433 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1435 $family = PVE
::Tools
::get_host_address_family
($node);
1438 my $port = PVE
::Tools
::next_vnc_port
($family);
1445 syslog
('info', "starting vnc proxy $upid\n");
1449 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1451 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1453 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1454 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1455 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1456 '-timeout', $timeout, '-authpath', $authpath,
1457 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1458 PVE
::Tools
::run_command
($cmd);
1461 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1463 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1465 my $sock = IO
::Socket
::IP-
>new(
1470 GetAddrInfoFlags
=> 0,
1471 ) or die "failed to create socket: $!\n";
1472 # Inside the worker we shouldn't have any previous alarms
1473 # running anyway...:
1475 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1477 accept(my $cli, $sock) or die "connection failed: $!\n";
1480 if (PVE
::Tools
::run_command
($cmd,
1481 output
=> '>&'.fileno($cli),
1482 input
=> '<&'.fileno($cli),
1485 die "Failed to run vncproxy.\n";
1492 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1494 PVE
::Tools
::wait_for_vnc_port
($port);
1505 __PACKAGE__-
>register_method({
1506 name
=> 'vncwebsocket',
1507 path
=> '{vmid}/vncwebsocket',
1510 description
=> "You also need to pass a valid ticket (vncticket).",
1511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1513 description
=> "Opens a weksocket for VNC traffic.",
1515 additionalProperties
=> 0,
1517 node
=> get_standard_option
('pve-node'),
1518 vmid
=> get_standard_option
('pve-vmid'),
1520 description
=> "Ticket from previous call to vncproxy.",
1525 description
=> "Port number returned by previous vncproxy call.",
1535 port
=> { type
=> 'string' },
1541 my $rpcenv = PVE
::RPCEnvironment
::get
();
1543 my $authuser = $rpcenv->get_user();
1545 my $vmid = $param->{vmid
};
1546 my $node = $param->{node
};
1548 my $authpath = "/vms/$vmid";
1550 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1552 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1554 # Note: VNC ports are acessible from outside, so we do not gain any
1555 # security if we verify that $param->{port} belongs to VM $vmid. This
1556 # check is done by verifying the VNC ticket (inside VNC protocol).
1558 my $port = $param->{port
};
1560 return { port
=> $port };
1563 __PACKAGE__-
>register_method({
1564 name
=> 'spiceproxy',
1565 path
=> '{vmid}/spiceproxy',
1570 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1572 description
=> "Returns a SPICE configuration to connect to the VM.",
1574 additionalProperties
=> 0,
1576 node
=> get_standard_option
('pve-node'),
1577 vmid
=> get_standard_option
('pve-vmid'),
1578 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1581 returns
=> get_standard_option
('remote-viewer-config'),
1585 my $rpcenv = PVE
::RPCEnvironment
::get
();
1587 my $authuser = $rpcenv->get_user();
1589 my $vmid = $param->{vmid
};
1590 my $node = $param->{node
};
1591 my $proxy = $param->{proxy
};
1593 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1594 my $title = "VM $vmid";
1595 $title .= " - ". $conf->{name
} if $conf->{name
};
1597 my $port = PVE
::QemuServer
::spice_port
($vmid);
1599 my ($ticket, undef, $remote_viewer_config) =
1600 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1602 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1603 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1605 return $remote_viewer_config;
1608 __PACKAGE__-
>register_method({
1610 path
=> '{vmid}/status',
1613 description
=> "Directory index",
1618 additionalProperties
=> 0,
1620 node
=> get_standard_option
('pve-node'),
1621 vmid
=> get_standard_option
('pve-vmid'),
1629 subdir
=> { type
=> 'string' },
1632 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1638 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1641 { subdir
=> 'current' },
1642 { subdir
=> 'start' },
1643 { subdir
=> 'stop' },
1649 __PACKAGE__-
>register_method({
1650 name
=> 'vm_status',
1651 path
=> '{vmid}/status/current',
1654 protected
=> 1, # qemu pid files are only readable by root
1655 description
=> "Get virtual machine status.",
1657 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1660 additionalProperties
=> 0,
1662 node
=> get_standard_option
('pve-node'),
1663 vmid
=> get_standard_option
('pve-vmid'),
1666 returns
=> { type
=> 'object' },
1671 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1673 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1674 my $status = $vmstatus->{$param->{vmid
}};
1676 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1678 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1683 __PACKAGE__-
>register_method({
1685 path
=> '{vmid}/status/start',
1689 description
=> "Start virtual machine.",
1691 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1694 additionalProperties
=> 0,
1696 node
=> get_standard_option
('pve-node'),
1697 vmid
=> get_standard_option
('pve-vmid',
1698 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1699 skiplock
=> get_standard_option
('skiplock'),
1700 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1701 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1704 enum
=> ['secure', 'insecure'],
1705 description
=> "Migration traffic is encrypted using an SSH " .
1706 "tunnel by default. On secure, completely private networks " .
1707 "this can be disabled to increase performance.",
1710 migration_network
=> {
1711 type
=> 'string', format
=> 'CIDR',
1712 description
=> "CIDR of the (sub) network that is used for migration.",
1715 machine
=> get_standard_option
('pve-qm-machine'),
1717 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1729 my $rpcenv = PVE
::RPCEnvironment
::get
();
1731 my $authuser = $rpcenv->get_user();
1733 my $node = extract_param
($param, 'node');
1735 my $vmid = extract_param
($param, 'vmid');
1737 my $machine = extract_param
($param, 'machine');
1739 my $stateuri = extract_param
($param, 'stateuri');
1740 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1741 if $stateuri && $authuser ne 'root@pam';
1743 my $skiplock = extract_param
($param, 'skiplock');
1744 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1745 if $skiplock && $authuser ne 'root@pam';
1747 my $migratedfrom = extract_param
($param, 'migratedfrom');
1748 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1749 if $migratedfrom && $authuser ne 'root@pam';
1751 my $migration_type = extract_param
($param, 'migration_type');
1752 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1753 if $migration_type && $authuser ne 'root@pam';
1755 my $migration_network = extract_param
($param, 'migration_network');
1756 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1757 if $migration_network && $authuser ne 'root@pam';
1759 my $targetstorage = extract_param
($param, 'targetstorage');
1760 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1761 if $targetstorage && $authuser ne 'root@pam';
1763 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1764 if $targetstorage && !$migratedfrom;
1766 # read spice ticket from STDIN
1768 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1769 if (defined(my $line = <>)) {
1771 $spice_ticket = $line;
1775 PVE
::Cluster
::check_cfs_quorum
();
1777 my $storecfg = PVE
::Storage
::config
();
1779 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1780 $rpcenv->{type
} ne 'ha') {
1785 my $service = "vm:$vmid";
1787 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1789 print "Requesting HA start for VM $vmid\n";
1791 PVE
::Tools
::run_command
($cmd);
1796 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1803 syslog
('info', "start VM $vmid: $upid\n");
1805 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1806 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1811 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1815 __PACKAGE__-
>register_method({
1817 path
=> '{vmid}/status/stop',
1821 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1822 "is akin to pulling the power plug of a running computer and may damage the VM data",
1824 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1827 additionalProperties
=> 0,
1829 node
=> get_standard_option
('pve-node'),
1830 vmid
=> get_standard_option
('pve-vmid',
1831 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1832 skiplock
=> get_standard_option
('skiplock'),
1833 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1835 description
=> "Wait maximal timeout seconds.",
1841 description
=> "Do not deactivate storage volumes.",
1854 my $rpcenv = PVE
::RPCEnvironment
::get
();
1856 my $authuser = $rpcenv->get_user();
1858 my $node = extract_param
($param, 'node');
1860 my $vmid = extract_param
($param, 'vmid');
1862 my $skiplock = extract_param
($param, 'skiplock');
1863 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1864 if $skiplock && $authuser ne 'root@pam';
1866 my $keepActive = extract_param
($param, 'keepActive');
1867 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1868 if $keepActive && $authuser ne 'root@pam';
1870 my $migratedfrom = extract_param
($param, 'migratedfrom');
1871 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1872 if $migratedfrom && $authuser ne 'root@pam';
1875 my $storecfg = PVE
::Storage
::config
();
1877 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1882 my $service = "vm:$vmid";
1884 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1886 print "Requesting HA stop for VM $vmid\n";
1888 PVE
::Tools
::run_command
($cmd);
1893 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1899 syslog
('info', "stop VM $vmid: $upid\n");
1901 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1902 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1907 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1911 __PACKAGE__-
>register_method({
1913 path
=> '{vmid}/status/reset',
1917 description
=> "Reset virtual machine.",
1919 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1922 additionalProperties
=> 0,
1924 node
=> get_standard_option
('pve-node'),
1925 vmid
=> get_standard_option
('pve-vmid',
1926 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1927 skiplock
=> get_standard_option
('skiplock'),
1936 my $rpcenv = PVE
::RPCEnvironment
::get
();
1938 my $authuser = $rpcenv->get_user();
1940 my $node = extract_param
($param, 'node');
1942 my $vmid = extract_param
($param, 'vmid');
1944 my $skiplock = extract_param
($param, 'skiplock');
1945 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1946 if $skiplock && $authuser ne 'root@pam';
1948 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1953 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1958 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1961 __PACKAGE__-
>register_method({
1962 name
=> 'vm_shutdown',
1963 path
=> '{vmid}/status/shutdown',
1967 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1968 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1970 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1973 additionalProperties
=> 0,
1975 node
=> get_standard_option
('pve-node'),
1976 vmid
=> get_standard_option
('pve-vmid',
1977 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1978 skiplock
=> get_standard_option
('skiplock'),
1980 description
=> "Wait maximal timeout seconds.",
1986 description
=> "Make sure the VM stops.",
1992 description
=> "Do not deactivate storage volumes.",
2005 my $rpcenv = PVE
::RPCEnvironment
::get
();
2007 my $authuser = $rpcenv->get_user();
2009 my $node = extract_param
($param, 'node');
2011 my $vmid = extract_param
($param, 'vmid');
2013 my $skiplock = extract_param
($param, 'skiplock');
2014 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2015 if $skiplock && $authuser ne 'root@pam';
2017 my $keepActive = extract_param
($param, 'keepActive');
2018 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2019 if $keepActive && $authuser ne 'root@pam';
2021 my $storecfg = PVE
::Storage
::config
();
2025 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2026 # otherwise, we will infer a shutdown command, but run into the timeout,
2027 # then when the vm is resumed, it will instantly shutdown
2029 # checking the qmp status here to get feedback to the gui/cli/api
2030 # and the status query should not take too long
2033 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2037 if (!$err && $qmpstatus->{status
} eq "paused") {
2038 if ($param->{forceStop
}) {
2039 warn "VM is paused - stop instead of shutdown\n";
2042 die "VM is paused - cannot shutdown\n";
2046 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2047 ($rpcenv->{type
} ne 'ha')) {
2052 my $service = "vm:$vmid";
2054 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2056 print "Requesting HA stop for VM $vmid\n";
2058 PVE
::Tools
::run_command
($cmd);
2063 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2070 syslog
('info', "shutdown VM $vmid: $upid\n");
2072 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2073 $shutdown, $param->{forceStop
}, $keepActive);
2078 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2082 __PACKAGE__-
>register_method({
2083 name
=> 'vm_suspend',
2084 path
=> '{vmid}/status/suspend',
2088 description
=> "Suspend virtual machine.",
2090 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2093 additionalProperties
=> 0,
2095 node
=> get_standard_option
('pve-node'),
2096 vmid
=> get_standard_option
('pve-vmid',
2097 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2098 skiplock
=> get_standard_option
('skiplock'),
2107 my $rpcenv = PVE
::RPCEnvironment
::get
();
2109 my $authuser = $rpcenv->get_user();
2111 my $node = extract_param
($param, 'node');
2113 my $vmid = extract_param
($param, 'vmid');
2115 my $skiplock = extract_param
($param, 'skiplock');
2116 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2117 if $skiplock && $authuser ne 'root@pam';
2119 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2124 syslog
('info', "suspend VM $vmid: $upid\n");
2126 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2131 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2134 __PACKAGE__-
>register_method({
2135 name
=> 'vm_resume',
2136 path
=> '{vmid}/status/resume',
2140 description
=> "Resume virtual machine.",
2142 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2145 additionalProperties
=> 0,
2147 node
=> get_standard_option
('pve-node'),
2148 vmid
=> get_standard_option
('pve-vmid',
2149 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2150 skiplock
=> get_standard_option
('skiplock'),
2151 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2161 my $rpcenv = PVE
::RPCEnvironment
::get
();
2163 my $authuser = $rpcenv->get_user();
2165 my $node = extract_param
($param, 'node');
2167 my $vmid = extract_param
($param, 'vmid');
2169 my $skiplock = extract_param
($param, 'skiplock');
2170 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2171 if $skiplock && $authuser ne 'root@pam';
2173 my $nocheck = extract_param
($param, 'nocheck');
2175 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2180 syslog
('info', "resume VM $vmid: $upid\n");
2182 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2187 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2190 __PACKAGE__-
>register_method({
2191 name
=> 'vm_sendkey',
2192 path
=> '{vmid}/sendkey',
2196 description
=> "Send key event to virtual machine.",
2198 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2201 additionalProperties
=> 0,
2203 node
=> get_standard_option
('pve-node'),
2204 vmid
=> get_standard_option
('pve-vmid',
2205 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2206 skiplock
=> get_standard_option
('skiplock'),
2208 description
=> "The key (qemu monitor encoding).",
2213 returns
=> { type
=> 'null'},
2217 my $rpcenv = PVE
::RPCEnvironment
::get
();
2219 my $authuser = $rpcenv->get_user();
2221 my $node = extract_param
($param, 'node');
2223 my $vmid = extract_param
($param, 'vmid');
2225 my $skiplock = extract_param
($param, 'skiplock');
2226 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2227 if $skiplock && $authuser ne 'root@pam';
2229 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2234 __PACKAGE__-
>register_method({
2235 name
=> 'vm_feature',
2236 path
=> '{vmid}/feature',
2240 description
=> "Check if feature for virtual machine is available.",
2242 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2245 additionalProperties
=> 0,
2247 node
=> get_standard_option
('pve-node'),
2248 vmid
=> get_standard_option
('pve-vmid'),
2250 description
=> "Feature to check.",
2252 enum
=> [ 'snapshot', 'clone', 'copy' ],
2254 snapname
=> get_standard_option
('pve-snapshot-name', {
2262 hasFeature
=> { type
=> 'boolean' },
2265 items
=> { type
=> 'string' },
2272 my $node = extract_param
($param, 'node');
2274 my $vmid = extract_param
($param, 'vmid');
2276 my $snapname = extract_param
($param, 'snapname');
2278 my $feature = extract_param
($param, 'feature');
2280 my $running = PVE
::QemuServer
::check_running
($vmid);
2282 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2285 my $snap = $conf->{snapshots
}->{$snapname};
2286 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2289 my $storecfg = PVE
::Storage
::config
();
2291 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2292 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2295 hasFeature
=> $hasFeature,
2296 nodes
=> [ keys %$nodelist ],
2300 __PACKAGE__-
>register_method({
2302 path
=> '{vmid}/clone',
2306 description
=> "Create a copy of virtual machine/template.",
2308 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2309 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2310 "'Datastore.AllocateSpace' on any used storage.",
2313 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2315 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2316 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2321 additionalProperties
=> 0,
2323 node
=> get_standard_option
('pve-node'),
2324 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2325 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2328 type
=> 'string', format
=> 'dns-name',
2329 description
=> "Set a name for the new VM.",
2334 description
=> "Description for the new VM.",
2338 type
=> 'string', format
=> 'pve-poolid',
2339 description
=> "Add the new VM to the specified pool.",
2341 snapname
=> get_standard_option
('pve-snapshot-name', {
2344 storage
=> get_standard_option
('pve-storage-id', {
2345 description
=> "Target storage for full clone.",
2350 description
=> "Target format for file storage.",
2354 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2359 description
=> "Create a full copy of all disk. This is always done when " .
2360 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2363 target
=> get_standard_option
('pve-node', {
2364 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2375 my $rpcenv = PVE
::RPCEnvironment
::get
();
2377 my $authuser = $rpcenv->get_user();
2379 my $node = extract_param
($param, 'node');
2381 my $vmid = extract_param
($param, 'vmid');
2383 my $newid = extract_param
($param, 'newid');
2385 my $pool = extract_param
($param, 'pool');
2387 if (defined($pool)) {
2388 $rpcenv->check_pool_exist($pool);
2391 my $snapname = extract_param
($param, 'snapname');
2393 my $storage = extract_param
($param, 'storage');
2395 my $format = extract_param
($param, 'format');
2397 my $target = extract_param
($param, 'target');
2399 my $localnode = PVE
::INotify
::nodename
();
2401 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2403 PVE
::Cluster
::check_node_exists
($target) if $target;
2405 my $storecfg = PVE
::Storage
::config
();
2408 # check if storage is enabled on local node
2409 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2411 # check if storage is available on target node
2412 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2413 # clone only works if target storage is shared
2414 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2415 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2419 PVE
::Cluster
::check_cfs_quorum
();
2421 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2423 # exclusive lock if VM is running - else shared lock is enough;
2424 my $shared_lock = $running ?
0 : 1;
2428 # do all tests after lock
2429 # we also try to do all tests before we fork the worker
2431 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2433 PVE
::QemuConfig-
>check_lock($conf);
2435 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2437 die "unexpected state change\n" if $verify_running != $running;
2439 die "snapshot '$snapname' does not exist\n"
2440 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2442 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2444 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2446 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2448 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2450 die "unable to create VM $newid: config file already exists\n"
2453 my $newconf = { lock => 'clone' };
2458 foreach my $opt (keys %$oldconf) {
2459 my $value = $oldconf->{$opt};
2461 # do not copy snapshot related info
2462 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2463 $opt eq 'vmstate' || $opt eq 'snapstate';
2465 # no need to copy unused images, because VMID(owner) changes anyways
2466 next if $opt =~ m/^unused\d+$/;
2468 # always change MAC! address
2469 if ($opt =~ m/^net(\d+)$/) {
2470 my $net = PVE
::QemuServer
::parse_net
($value);
2471 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2472 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2473 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2474 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2475 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2476 die "unable to parse drive options for '$opt'\n" if !$drive;
2477 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2478 $newconf->{$opt} = $value; # simply copy configuration
2480 if ($param->{full
}) {
2481 die "Full clone feature is not supported for drive '$opt'\n"
2482 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2483 $fullclone->{$opt} = 1;
2485 # not full means clone instead of copy
2486 die "Linked clone feature is not supported for drive '$opt'\n"
2487 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2489 $drives->{$opt} = $drive;
2490 push @$vollist, $drive->{file
};
2493 # copy everything else
2494 $newconf->{$opt} = $value;
2498 # auto generate a new uuid
2499 my ($uuid, $uuid_str);
2500 UUID
::generate
($uuid);
2501 UUID
::unparse
($uuid, $uuid_str);
2502 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2503 $smbios1->{uuid
} = $uuid_str;
2504 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2506 delete $newconf->{template
};
2508 if ($param->{name
}) {
2509 $newconf->{name
} = $param->{name
};
2511 if ($oldconf->{name
}) {
2512 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2514 $newconf->{name
} = "Copy-of-VM-$vmid";
2518 if ($param->{description
}) {
2519 $newconf->{description
} = $param->{description
};
2522 # create empty/temp config - this fails if VM already exists on other node
2523 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2528 my $newvollist = [];
2532 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2534 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2536 my $total_jobs = scalar(keys %{$drives});
2539 foreach my $opt (keys %$drives) {
2540 my $drive = $drives->{$opt};
2541 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2543 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2544 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2545 $jobs, $skipcomplete, $oldconf->{agent
});
2547 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2549 PVE
::QemuConfig-
>write_config($newid, $newconf);
2553 delete $newconf->{lock};
2554 PVE
::QemuConfig-
>write_config($newid, $newconf);
2557 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2558 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2559 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2561 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2562 die "Failed to move config to node '$target' - rename failed: $!\n"
2563 if !rename($conffile, $newconffile);
2566 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2571 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2573 sleep 1; # some storage like rbd need to wait before release volume - really?
2575 foreach my $volid (@$newvollist) {
2576 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2579 die "clone failed: $err";
2585 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2587 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2590 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2591 # Aquire exclusive lock lock for $newid
2592 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2597 __PACKAGE__-
>register_method({
2598 name
=> 'move_vm_disk',
2599 path
=> '{vmid}/move_disk',
2603 description
=> "Move volume to different storage.",
2605 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2607 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2608 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2612 additionalProperties
=> 0,
2614 node
=> get_standard_option
('pve-node'),
2615 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2618 description
=> "The disk you want to move.",
2619 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2621 storage
=> get_standard_option
('pve-storage-id', {
2622 description
=> "Target storage.",
2623 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2627 description
=> "Target Format.",
2628 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2633 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2639 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2647 description
=> "the task ID.",
2652 my $rpcenv = PVE
::RPCEnvironment
::get
();
2654 my $authuser = $rpcenv->get_user();
2656 my $node = extract_param
($param, 'node');
2658 my $vmid = extract_param
($param, 'vmid');
2660 my $digest = extract_param
($param, 'digest');
2662 my $disk = extract_param
($param, 'disk');
2664 my $storeid = extract_param
($param, 'storage');
2666 my $format = extract_param
($param, 'format');
2668 my $storecfg = PVE
::Storage
::config
();
2670 my $updatefn = sub {
2672 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2674 PVE
::QemuConfig-
>check_lock($conf);
2676 die "checksum missmatch (file change by other user?)\n"
2677 if $digest && $digest ne $conf->{digest
};
2679 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2681 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2683 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2685 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2688 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2689 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2693 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2694 (!$format || !$oldfmt || $oldfmt eq $format);
2696 # this only checks snapshots because $disk is passed!
2697 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2698 die "you can't move a disk with snapshots and delete the source\n"
2699 if $snapshotted && $param->{delete};
2701 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2703 my $running = PVE
::QemuServer
::check_running
($vmid);
2705 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2709 my $newvollist = [];
2712 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2714 warn "moving disk with snapshots, snapshots will not be moved!\n"
2717 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2718 $vmid, $storeid, $format, 1, $newvollist);
2720 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2722 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2724 # convert moved disk to base if part of template
2725 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2726 if PVE
::QemuConfig-
>is_template($conf);
2728 PVE
::QemuConfig-
>write_config($vmid, $conf);
2731 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2732 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2739 foreach my $volid (@$newvollist) {
2740 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2743 die "storage migration failed: $err";
2746 if ($param->{delete}) {
2748 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2749 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2755 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2758 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2761 __PACKAGE__-
>register_method({
2762 name
=> 'migrate_vm',
2763 path
=> '{vmid}/migrate',
2767 description
=> "Migrate virtual machine. Creates a new migration task.",
2769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2772 additionalProperties
=> 0,
2774 node
=> get_standard_option
('pve-node'),
2775 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2776 target
=> get_standard_option
('pve-node', {
2777 description
=> "Target node.",
2778 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2782 description
=> "Use online/live migration.",
2787 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2792 enum
=> ['secure', 'insecure'],
2793 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2796 migration_network
=> {
2797 type
=> 'string', format
=> 'CIDR',
2798 description
=> "CIDR of the (sub) network that is used for migration.",
2801 "with-local-disks" => {
2803 description
=> "Enable live storage migration for local disk",
2806 targetstorage
=> get_standard_option
('pve-storage-id', {
2807 description
=> "Default target storage.",
2809 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2815 description
=> "the task ID.",
2820 my $rpcenv = PVE
::RPCEnvironment
::get
();
2822 my $authuser = $rpcenv->get_user();
2824 my $target = extract_param
($param, 'target');
2826 my $localnode = PVE
::INotify
::nodename
();
2827 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2829 PVE
::Cluster
::check_cfs_quorum
();
2831 PVE
::Cluster
::check_node_exists
($target);
2833 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2835 my $vmid = extract_param
($param, 'vmid');
2837 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2838 if !$param->{online
} && $param->{targetstorage
};
2840 raise_param_exc
({ force
=> "Only root may use this option." })
2841 if $param->{force
} && $authuser ne 'root@pam';
2843 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2844 if $param->{migration_type
} && $authuser ne 'root@pam';
2846 # allow root only until better network permissions are available
2847 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2848 if $param->{migration_network
} && $authuser ne 'root@pam';
2851 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2853 # try to detect errors early
2855 PVE
::QemuConfig-
>check_lock($conf);
2857 if (PVE
::QemuServer
::check_running
($vmid)) {
2858 die "cant migrate running VM without --online\n"
2859 if !$param->{online
};
2862 my $storecfg = PVE
::Storage
::config
();
2864 if( $param->{targetstorage
}) {
2865 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2867 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2870 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2875 my $service = "vm:$vmid";
2877 my $cmd = ['ha-manager', 'migrate', $service, $target];
2879 print "Requesting HA migration for VM $vmid to node $target\n";
2881 PVE
::Tools
::run_command
($cmd);
2886 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2891 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2895 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2898 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2903 __PACKAGE__-
>register_method({
2905 path
=> '{vmid}/monitor',
2909 description
=> "Execute Qemu monitor commands.",
2911 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2915 additionalProperties
=> 0,
2917 node
=> get_standard_option
('pve-node'),
2918 vmid
=> get_standard_option
('pve-vmid'),
2921 description
=> "The monitor command.",
2925 returns
=> { type
=> 'string'},
2929 my $rpcenv = PVE
::RPCEnvironment
::get
();
2930 my $authuser = $rpcenv->get_user();
2933 my $command = shift;
2934 return $command =~ m/^\s*info(\s+|$)/
2935 || $command =~ m/^\s*help\s*$/;
2938 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2939 if !&$is_ro($param->{command
});
2941 my $vmid = $param->{vmid
};
2943 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2947 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2949 $res = "ERROR: $@" if $@;
2954 my $guest_agent_commands = [
2962 'network-get-interfaces',
2965 'get-memory-blocks',
2966 'get-memory-block-info',
2973 __PACKAGE__-
>register_method({
2975 path
=> '{vmid}/agent',
2979 description
=> "Execute Qemu Guest Agent commands.",
2981 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2984 additionalProperties
=> 0,
2986 node
=> get_standard_option
('pve-node'),
2987 vmid
=> get_standard_option
('pve-vmid', {
2988 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2991 description
=> "The QGA command.",
2992 enum
=> $guest_agent_commands,
2998 description
=> "Returns an object with a single `result` property. The type of that
2999 property depends on the executed command.",
3004 my $vmid = $param->{vmid
};
3006 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3008 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3009 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3011 my $cmd = $param->{command
};
3013 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3015 return { result
=> $res };
3018 __PACKAGE__-
>register_method({
3019 name
=> 'resize_vm',
3020 path
=> '{vmid}/resize',
3024 description
=> "Extend volume size.",
3026 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3029 additionalProperties
=> 0,
3031 node
=> get_standard_option
('pve-node'),
3032 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3033 skiplock
=> get_standard_option
('skiplock'),
3036 description
=> "The disk you want to resize.",
3037 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3041 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3042 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.",
3046 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3052 returns
=> { type
=> 'null'},
3056 my $rpcenv = PVE
::RPCEnvironment
::get
();
3058 my $authuser = $rpcenv->get_user();
3060 my $node = extract_param
($param, 'node');
3062 my $vmid = extract_param
($param, 'vmid');
3064 my $digest = extract_param
($param, 'digest');
3066 my $disk = extract_param
($param, 'disk');
3068 my $sizestr = extract_param
($param, 'size');
3070 my $skiplock = extract_param
($param, 'skiplock');
3071 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3072 if $skiplock && $authuser ne 'root@pam';
3074 my $storecfg = PVE
::Storage
::config
();
3076 my $updatefn = sub {
3078 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3080 die "checksum missmatch (file change by other user?)\n"
3081 if $digest && $digest ne $conf->{digest
};
3082 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3084 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3086 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3088 my (undef, undef, undef, undef, undef, undef, $format) =
3089 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3091 die "can't resize volume: $disk if snapshot exists\n"
3092 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3094 my $volid = $drive->{file
};
3096 die "disk '$disk' has no associated volume\n" if !$volid;
3098 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3100 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3102 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3104 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3105 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3107 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3108 my ($ext, $newsize, $unit) = ($1, $2, $4);
3111 $newsize = $newsize * 1024;
3112 } elsif ($unit eq 'M') {
3113 $newsize = $newsize * 1024 * 1024;
3114 } elsif ($unit eq 'G') {
3115 $newsize = $newsize * 1024 * 1024 * 1024;
3116 } elsif ($unit eq 'T') {
3117 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3120 $newsize += $size if $ext;
3121 $newsize = int($newsize);
3123 die "shrinking disks is not supported\n" if $newsize < $size;
3125 return if $size == $newsize;
3127 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3129 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3131 $drive->{size
} = $newsize;
3132 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3134 PVE
::QemuConfig-
>write_config($vmid, $conf);
3137 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3141 __PACKAGE__-
>register_method({
3142 name
=> 'snapshot_list',
3143 path
=> '{vmid}/snapshot',
3145 description
=> "List all snapshots.",
3147 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3150 protected
=> 1, # qemu pid files are only readable by root
3152 additionalProperties
=> 0,
3154 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3155 node
=> get_standard_option
('pve-node'),
3164 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3169 my $vmid = $param->{vmid
};
3171 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3172 my $snaphash = $conf->{snapshots
} || {};
3176 foreach my $name (keys %$snaphash) {
3177 my $d = $snaphash->{$name};
3180 snaptime
=> $d->{snaptime
} || 0,
3181 vmstate
=> $d->{vmstate
} ?
1 : 0,
3182 description
=> $d->{description
} || '',
3184 $item->{parent
} = $d->{parent
} if $d->{parent
};
3185 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3189 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3190 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3191 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3193 push @$res, $current;
3198 __PACKAGE__-
>register_method({
3200 path
=> '{vmid}/snapshot',
3204 description
=> "Snapshot a VM.",
3206 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3209 additionalProperties
=> 0,
3211 node
=> get_standard_option
('pve-node'),
3212 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3213 snapname
=> get_standard_option
('pve-snapshot-name'),
3217 description
=> "Save the vmstate",
3222 description
=> "A textual description or comment.",
3228 description
=> "the task ID.",
3233 my $rpcenv = PVE
::RPCEnvironment
::get
();
3235 my $authuser = $rpcenv->get_user();
3237 my $node = extract_param
($param, 'node');
3239 my $vmid = extract_param
($param, 'vmid');
3241 my $snapname = extract_param
($param, 'snapname');
3243 die "unable to use snapshot name 'current' (reserved name)\n"
3244 if $snapname eq 'current';
3247 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3248 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3249 $param->{description
});
3252 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3255 __PACKAGE__-
>register_method({
3256 name
=> 'snapshot_cmd_idx',
3257 path
=> '{vmid}/snapshot/{snapname}',
3264 additionalProperties
=> 0,
3266 vmid
=> get_standard_option
('pve-vmid'),
3267 node
=> get_standard_option
('pve-node'),
3268 snapname
=> get_standard_option
('pve-snapshot-name'),
3277 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3284 push @$res, { cmd
=> 'rollback' };
3285 push @$res, { cmd
=> 'config' };
3290 __PACKAGE__-
>register_method({
3291 name
=> 'update_snapshot_config',
3292 path
=> '{vmid}/snapshot/{snapname}/config',
3296 description
=> "Update snapshot metadata.",
3298 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3301 additionalProperties
=> 0,
3303 node
=> get_standard_option
('pve-node'),
3304 vmid
=> get_standard_option
('pve-vmid'),
3305 snapname
=> get_standard_option
('pve-snapshot-name'),
3309 description
=> "A textual description or comment.",
3313 returns
=> { type
=> 'null' },
3317 my $rpcenv = PVE
::RPCEnvironment
::get
();
3319 my $authuser = $rpcenv->get_user();
3321 my $vmid = extract_param
($param, 'vmid');
3323 my $snapname = extract_param
($param, 'snapname');
3325 return undef if !defined($param->{description
});
3327 my $updatefn = sub {
3329 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3331 PVE
::QemuConfig-
>check_lock($conf);
3333 my $snap = $conf->{snapshots
}->{$snapname};
3335 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3337 $snap->{description
} = $param->{description
} if defined($param->{description
});
3339 PVE
::QemuConfig-
>write_config($vmid, $conf);
3342 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3347 __PACKAGE__-
>register_method({
3348 name
=> 'get_snapshot_config',
3349 path
=> '{vmid}/snapshot/{snapname}/config',
3352 description
=> "Get snapshot configuration",
3354 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3357 additionalProperties
=> 0,
3359 node
=> get_standard_option
('pve-node'),
3360 vmid
=> get_standard_option
('pve-vmid'),
3361 snapname
=> get_standard_option
('pve-snapshot-name'),
3364 returns
=> { type
=> "object" },
3368 my $rpcenv = PVE
::RPCEnvironment
::get
();
3370 my $authuser = $rpcenv->get_user();
3372 my $vmid = extract_param
($param, 'vmid');
3374 my $snapname = extract_param
($param, 'snapname');
3376 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3378 my $snap = $conf->{snapshots
}->{$snapname};
3380 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3385 __PACKAGE__-
>register_method({
3387 path
=> '{vmid}/snapshot/{snapname}/rollback',
3391 description
=> "Rollback VM state to specified snapshot.",
3393 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3396 additionalProperties
=> 0,
3398 node
=> get_standard_option
('pve-node'),
3399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3400 snapname
=> get_standard_option
('pve-snapshot-name'),
3405 description
=> "the task ID.",
3410 my $rpcenv = PVE
::RPCEnvironment
::get
();
3412 my $authuser = $rpcenv->get_user();
3414 my $node = extract_param
($param, 'node');
3416 my $vmid = extract_param
($param, 'vmid');
3418 my $snapname = extract_param
($param, 'snapname');
3421 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3422 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3426 # hold migration lock, this makes sure that nobody create replication snapshots
3427 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3430 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3433 __PACKAGE__-
>register_method({
3434 name
=> 'delsnapshot',
3435 path
=> '{vmid}/snapshot/{snapname}',
3439 description
=> "Delete a VM snapshot.",
3441 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3444 additionalProperties
=> 0,
3446 node
=> get_standard_option
('pve-node'),
3447 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3448 snapname
=> get_standard_option
('pve-snapshot-name'),
3452 description
=> "For removal from config file, even if removing disk snapshots fails.",
3458 description
=> "the task ID.",
3463 my $rpcenv = PVE
::RPCEnvironment
::get
();
3465 my $authuser = $rpcenv->get_user();
3467 my $node = extract_param
($param, 'node');
3469 my $vmid = extract_param
($param, 'vmid');
3471 my $snapname = extract_param
($param, 'snapname');
3474 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3475 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3478 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3481 __PACKAGE__-
>register_method({
3483 path
=> '{vmid}/template',
3487 description
=> "Create a Template.",
3489 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3490 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3493 additionalProperties
=> 0,
3495 node
=> get_standard_option
('pve-node'),
3496 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3500 description
=> "If you want to convert only 1 disk to base image.",
3501 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3506 returns
=> { type
=> 'null'},
3510 my $rpcenv = PVE
::RPCEnvironment
::get
();
3512 my $authuser = $rpcenv->get_user();
3514 my $node = extract_param
($param, 'node');
3516 my $vmid = extract_param
($param, 'vmid');
3518 my $disk = extract_param
($param, 'disk');
3520 my $updatefn = sub {
3522 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3524 PVE
::QemuConfig-
>check_lock($conf);
3526 die "unable to create template, because VM contains snapshots\n"
3527 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3529 die "you can't convert a template to a template\n"
3530 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3532 die "you can't convert a VM to template if VM is running\n"
3533 if PVE
::QemuServer
::check_running
($vmid);
3536 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3539 $conf->{template
} = 1;
3540 PVE
::QemuConfig-
>write_config($vmid, $conf);
3542 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3545 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);