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);
83 my $check_storage_access_clone = sub {
84 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
88 PVE
::QemuServer
::foreach_drive
($conf, sub {
89 my ($ds, $drive) = @_;
91 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
93 my $volid = $drive->{file
};
95 return if !$volid || $volid eq 'none';
98 if ($volid eq 'cdrom') {
99 $rpcenv->check($authuser, "/", ['Sys.Console']);
101 # we simply allow access
102 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
103 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
104 $sharedvm = 0 if !$scfg->{shared
};
108 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
109 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
110 $sharedvm = 0 if !$scfg->{shared
};
112 $sid = $storage if $storage;
113 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
120 # Note: $pool is only needed when creating a VM, because pool permissions
121 # are automatically inherited if VM already exists inside a pool.
122 my $create_disks = sub {
123 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
130 my ($ds, $disk) = @_;
132 my $volid = $disk->{file
};
134 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
135 delete $disk->{size
};
136 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
137 } elsif ($volid =~ $NEW_DISK_RE) {
138 my ($storeid, $size) = ($2 || $default_storage, $3);
139 die "no storage ID specified (and no default storage)\n" if !$storeid;
140 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
141 my $fmt = $disk->{format
} || $defformat;
143 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
146 if ($ds eq 'efidisk0') {
148 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
149 die "uefi vars image not found\n" if ! -f
$ovmfvars;
150 $size = PVE
::Tools
::convert_size
(-s
$ovmfvars, 'b' => 'kb');
151 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
152 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
153 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
154 my $qemufmt = PVE
::QemuServer
::qemu_img_format
($scfg, $volname);
155 my $path = PVE
::Storage
::path
($storecfg, $volid);
156 my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt];
157 push @$efidiskcmd, $ovmfvars;
158 push @$efidiskcmd, $path;
160 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
162 eval { PVE
::Tools
::run_command
($efidiskcmd); };
164 die "Copying of EFI Vars image failed: $err" if $err;
166 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
168 push @$vollist, $volid;
169 $disk->{file
} = $volid;
170 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
171 delete $disk->{format
}; # no longer needed
172 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
175 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
177 my $volid_is_new = 1;
180 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
181 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
186 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
188 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
190 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
192 die "volume $volid does not exists\n" if !$size;
194 $disk->{size
} = $size;
197 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
201 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
203 # free allocated images on error
205 syslog
('err', "VM $vmid creating disks failed");
206 foreach my $volid (@$vollist) {
207 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
213 # modify vm config if everything went well
214 foreach my $ds (keys %$res) {
215 $conf->{$ds} = $res->{$ds};
232 my $memoryoptions = {
238 my $hwtypeoptions = {
250 my $generaloptions = {
257 'migrate_downtime' => 1,
258 'migrate_speed' => 1,
270 my $vmpoweroptions = {
279 my $check_vm_modify_config_perm = sub {
280 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
282 return 1 if $authuser eq 'root@pam';
284 foreach my $opt (@$key_list) {
285 # disk checks need to be done somewhere else
286 next if PVE
::QemuServer
::is_valid_drivename
($opt);
287 next if $opt eq 'cdrom';
288 next if $opt =~ m/^unused\d+$/;
290 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
291 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
292 } elsif ($memoryoptions->{$opt}) {
293 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
294 } elsif ($hwtypeoptions->{$opt}) {
295 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
296 } elsif ($generaloptions->{$opt}) {
297 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
298 # special case for startup since it changes host behaviour
299 if ($opt eq 'startup') {
300 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
302 } elsif ($vmpoweroptions->{$opt}) {
303 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
304 } elsif ($diskoptions->{$opt}) {
305 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
306 } elsif ($opt =~ m/^net\d+$/) {
307 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
309 # catches usb\d+, hostpci\d+, args, lock, etc.
310 # new options will be checked here
311 die "only root can set '$opt' config\n";
318 __PACKAGE__-
>register_method({
322 description
=> "Virtual machine index (per node).",
324 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
328 protected
=> 1, # qemu pid files are only readable by root
330 additionalProperties
=> 0,
332 node
=> get_standard_option
('pve-node'),
336 description
=> "Determine the full status of active VMs.",
346 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
351 my $rpcenv = PVE
::RPCEnvironment
::get
();
352 my $authuser = $rpcenv->get_user();
354 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
357 foreach my $vmid (keys %$vmstatus) {
358 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
360 my $data = $vmstatus->{$vmid};
361 $data->{vmid
} = int($vmid);
370 __PACKAGE__-
>register_method({
374 description
=> "Create or restore a virtual machine.",
376 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
377 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
378 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
379 user
=> 'all', # check inside
384 additionalProperties
=> 0,
385 properties
=> PVE
::QemuServer
::json_config_properties
(
387 node
=> get_standard_option
('pve-node'),
388 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
390 description
=> "The backup file.",
394 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
396 storage
=> get_standard_option
('pve-storage-id', {
397 description
=> "Default storage.",
399 completion
=> \
&PVE
::QemuServer
::complete_storage
,
404 description
=> "Allow to overwrite existing VM.",
405 requires
=> 'archive',
410 description
=> "Assign a unique random ethernet address.",
411 requires
=> 'archive',
415 type
=> 'string', format
=> 'pve-poolid',
416 description
=> "Add the VM to the specified pool.",
426 my $rpcenv = PVE
::RPCEnvironment
::get
();
428 my $authuser = $rpcenv->get_user();
430 my $node = extract_param
($param, 'node');
432 my $vmid = extract_param
($param, 'vmid');
434 my $archive = extract_param
($param, 'archive');
436 my $storage = extract_param
($param, 'storage');
438 my $force = extract_param
($param, 'force');
440 my $unique = extract_param
($param, 'unique');
442 my $pool = extract_param
($param, 'pool');
444 my $filename = PVE
::QemuConfig-
>config_file($vmid);
446 my $storecfg = PVE
::Storage
::config
();
448 PVE
::Cluster
::check_cfs_quorum
();
450 if (defined($pool)) {
451 $rpcenv->check_pool_exist($pool);
454 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
455 if defined($storage);
457 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
459 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
461 } elsif ($archive && $force && (-f
$filename) &&
462 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
463 # OK: user has VM.Backup permissions, and want to restore an existing VM
469 &$resolve_cdrom_alias($param);
471 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
473 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
475 foreach my $opt (keys %$param) {
476 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
477 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
478 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
480 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
481 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
485 PVE
::QemuServer
::add_random_macs
($param);
487 my $keystr = join(' ', keys %$param);
488 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
490 if ($archive eq '-') {
491 die "pipe requires cli environment\n"
492 if $rpcenv->{type
} ne 'cli';
494 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
495 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
499 my $restorefn = sub {
500 my $vmlist = PVE
::Cluster
::get_vmlist
();
501 if ($vmlist->{ids
}->{$vmid}) {
502 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
503 if ($current_node eq $node) {
504 my $conf = PVE
::QemuConfig-
>load_config($vmid);
506 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
508 die "unable to restore vm $vmid - config file already exists\n"
511 die "unable to restore vm $vmid - vm is running\n"
512 if PVE
::QemuServer
::check_running
($vmid);
514 die "unable to restore vm $vmid - vm is a template\n"
515 if PVE
::QemuConfig-
>is_template($conf);
518 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
523 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
526 unique
=> $unique });
528 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
531 # ensure no old replication state are exists
532 PVE
::ReplicationState
::delete_guest_states
($vmid);
534 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
540 PVE
::Cluster
::check_vmid_unused
($vmid);
542 # ensure no old replication state are exists
543 PVE
::ReplicationState
::delete_guest_states
($vmid);
553 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
555 # try to be smart about bootdisk
556 my @disks = PVE
::QemuServer
::valid_drive_names
();
558 foreach my $ds (reverse @disks) {
559 next if !$conf->{$ds};
560 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
561 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
565 if (!$conf->{bootdisk
} && $firstdisk) {
566 $conf->{bootdisk
} = $firstdisk;
569 # auto generate uuid if user did not specify smbios1 option
570 if (!$conf->{smbios1
}) {
571 my ($uuid, $uuid_str);
572 UUID
::generate
($uuid);
573 UUID
::unparse
($uuid, $uuid_str);
574 $conf->{smbios1
} = "uuid=$uuid_str";
577 PVE
::QemuConfig-
>write_config($vmid, $conf);
583 foreach my $volid (@$vollist) {
584 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
587 die "create failed - $err";
590 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
593 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
596 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
599 __PACKAGE__-
>register_method({
604 description
=> "Directory index",
609 additionalProperties
=> 0,
611 node
=> get_standard_option
('pve-node'),
612 vmid
=> get_standard_option
('pve-vmid'),
620 subdir
=> { type
=> 'string' },
623 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
629 { subdir
=> 'config' },
630 { subdir
=> 'pending' },
631 { subdir
=> 'status' },
632 { subdir
=> 'unlink' },
633 { subdir
=> 'vncproxy' },
634 { subdir
=> 'migrate' },
635 { subdir
=> 'resize' },
636 { subdir
=> 'move' },
638 { subdir
=> 'rrddata' },
639 { subdir
=> 'monitor' },
640 { subdir
=> 'agent' },
641 { subdir
=> 'snapshot' },
642 { subdir
=> 'spiceproxy' },
643 { subdir
=> 'sendkey' },
644 { subdir
=> 'firewall' },
650 __PACKAGE__-
>register_method ({
651 subclass
=> "PVE::API2::Firewall::VM",
652 path
=> '{vmid}/firewall',
655 __PACKAGE__-
>register_method({
657 path
=> '{vmid}/rrd',
659 protected
=> 1, # fixme: can we avoid that?
661 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
663 description
=> "Read VM RRD statistics (returns PNG)",
665 additionalProperties
=> 0,
667 node
=> get_standard_option
('pve-node'),
668 vmid
=> get_standard_option
('pve-vmid'),
670 description
=> "Specify the time frame you are interested in.",
672 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
675 description
=> "The list of datasources you want to display.",
676 type
=> 'string', format
=> 'pve-configid-list',
679 description
=> "The RRD consolidation function",
681 enum
=> [ 'AVERAGE', 'MAX' ],
689 filename
=> { type
=> 'string' },
695 return PVE
::Cluster
::create_rrd_graph
(
696 "pve2-vm/$param->{vmid}", $param->{timeframe
},
697 $param->{ds
}, $param->{cf
});
701 __PACKAGE__-
>register_method({
703 path
=> '{vmid}/rrddata',
705 protected
=> 1, # fixme: can we avoid that?
707 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
709 description
=> "Read VM RRD statistics",
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
714 vmid
=> get_standard_option
('pve-vmid'),
716 description
=> "Specify the time frame you are interested in.",
718 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
721 description
=> "The RRD consolidation function",
723 enum
=> [ 'AVERAGE', 'MAX' ],
738 return PVE
::Cluster
::create_rrd_data
(
739 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
743 __PACKAGE__-
>register_method({
745 path
=> '{vmid}/config',
748 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
750 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
753 additionalProperties
=> 0,
755 node
=> get_standard_option
('pve-node'),
756 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
758 description
=> "Get current values (instead of pending values).",
770 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
777 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
779 delete $conf->{snapshots
};
781 if (!$param->{current
}) {
782 foreach my $opt (keys %{$conf->{pending
}}) {
783 next if $opt eq 'delete';
784 my $value = $conf->{pending
}->{$opt};
785 next if ref($value); # just to be sure
786 $conf->{$opt} = $value;
788 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
789 foreach my $opt (keys %$pending_delete_hash) {
790 delete $conf->{$opt} if $conf->{$opt};
794 delete $conf->{pending
};
799 __PACKAGE__-
>register_method({
800 name
=> 'vm_pending',
801 path
=> '{vmid}/pending',
804 description
=> "Get virtual machine configuration, including pending changes.",
806 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
809 additionalProperties
=> 0,
811 node
=> get_standard_option
('pve-node'),
812 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
821 description
=> "Configuration option name.",
825 description
=> "Current value.",
830 description
=> "Pending value.",
835 description
=> "Indicates a pending delete request if present and not 0. " .
836 "The value 2 indicates a force-delete request.",
848 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
850 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
854 foreach my $opt (keys %$conf) {
855 next if ref($conf->{$opt});
856 my $item = { key
=> $opt };
857 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
858 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
859 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
863 foreach my $opt (keys %{$conf->{pending
}}) {
864 next if $opt eq 'delete';
865 next if ref($conf->{pending
}->{$opt}); # just to be sure
866 next if defined($conf->{$opt});
867 my $item = { key
=> $opt };
868 $item->{pending
} = $conf->{pending
}->{$opt};
872 while (my ($opt, $force) = each %$pending_delete_hash) {
873 next if $conf->{pending
}->{$opt}; # just to be sure
874 next if $conf->{$opt};
875 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
882 # POST/PUT {vmid}/config implementation
884 # The original API used PUT (idempotent) an we assumed that all operations
885 # are fast. But it turned out that almost any configuration change can
886 # involve hot-plug actions, or disk alloc/free. Such actions can take long
887 # time to complete and have side effects (not idempotent).
889 # The new implementation uses POST and forks a worker process. We added
890 # a new option 'background_delay'. If specified we wait up to
891 # 'background_delay' second for the worker task to complete. It returns null
892 # if the task is finished within that time, else we return the UPID.
894 my $update_vm_api = sub {
895 my ($param, $sync) = @_;
897 my $rpcenv = PVE
::RPCEnvironment
::get
();
899 my $authuser = $rpcenv->get_user();
901 my $node = extract_param
($param, 'node');
903 my $vmid = extract_param
($param, 'vmid');
905 my $digest = extract_param
($param, 'digest');
907 my $background_delay = extract_param
($param, 'background_delay');
909 my @paramarr = (); # used for log message
910 foreach my $key (sort keys %$param) {
911 push @paramarr, "-$key", $param->{$key};
914 my $skiplock = extract_param
($param, 'skiplock');
915 raise_param_exc
({ skiplock
=> "Only root may use this option." })
916 if $skiplock && $authuser ne 'root@pam';
918 my $delete_str = extract_param
($param, 'delete');
920 my $revert_str = extract_param
($param, 'revert');
922 my $force = extract_param
($param, 'force');
924 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
926 my $storecfg = PVE
::Storage
::config
();
928 my $defaults = PVE
::QemuServer
::load_defaults
();
930 &$resolve_cdrom_alias($param);
932 # now try to verify all parameters
935 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
936 if (!PVE
::QemuServer
::option_exists
($opt)) {
937 raise_param_exc
({ revert
=> "unknown option '$opt'" });
940 raise_param_exc
({ delete => "you can't use '-$opt' and " .
941 "-revert $opt' at the same time" })
942 if defined($param->{$opt});
948 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
949 $opt = 'ide2' if $opt eq 'cdrom';
951 raise_param_exc
({ delete => "you can't use '-$opt' and " .
952 "-delete $opt' at the same time" })
953 if defined($param->{$opt});
955 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
956 "-revert $opt' at the same time" })
959 if (!PVE
::QemuServer
::option_exists
($opt)) {
960 raise_param_exc
({ delete => "unknown option '$opt'" });
966 my $repl_conf = PVE
::ReplicationConfig-
>new();
967 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
968 my $check_replication = sub {
970 return if !$is_replicated;
971 my $volid = $drive->{file
};
972 return if !$volid || !($drive->{replicate
}//1);
973 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
974 my ($storeid, $format);
975 if ($volid =~ $NEW_DISK_RE) {
977 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
979 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
980 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
982 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
983 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
984 return if $scfg->{shared
};
985 die "cannot add non-replicatable volume to a replicated VM\n";
988 foreach my $opt (keys %$param) {
989 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
991 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
992 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
993 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
994 $check_replication->($drive);
995 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
996 } elsif ($opt =~ m/^net(\d+)$/) {
998 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
999 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1003 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1005 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1007 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1009 my $updatefn = sub {
1011 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1013 die "checksum missmatch (file change by other user?)\n"
1014 if $digest && $digest ne $conf->{digest
};
1016 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1018 foreach my $opt (keys %$revert) {
1019 if (defined($conf->{$opt})) {
1020 $param->{$opt} = $conf->{$opt};
1021 } elsif (defined($conf->{pending
}->{$opt})) {
1026 if ($param->{memory
} || defined($param->{balloon
})) {
1027 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1028 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1030 die "balloon value too large (must be smaller than assigned memory)\n"
1031 if $balloon && $balloon > $maxmem;
1034 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1038 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1040 # write updates to pending section
1042 my $modified = {}; # record what $option we modify
1044 foreach my $opt (@delete) {
1045 $modified->{$opt} = 1;
1046 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1047 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1048 warn "cannot delete '$opt' - not set in current configuration!\n";
1049 $modified->{$opt} = 0;
1053 if ($opt =~ m/^unused/) {
1054 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1055 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1056 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1057 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1058 delete $conf->{$opt};
1059 PVE
::QemuConfig-
>write_config($vmid, $conf);
1061 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1062 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1063 $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});
1066 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1067 PVE
::QemuConfig-
>write_config($vmid, $conf);
1069 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1070 PVE
::QemuConfig-
>write_config($vmid, $conf);
1074 foreach my $opt (keys %$param) { # add/change
1075 $modified->{$opt} = 1;
1076 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1077 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1079 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1080 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1081 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1082 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1084 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1086 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1087 if defined($conf->{pending
}->{$opt});
1089 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1091 $conf->{pending
}->{$opt} = $param->{$opt};
1093 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1094 PVE
::QemuConfig-
>write_config($vmid, $conf);
1097 # remove pending changes when nothing changed
1098 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1099 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1100 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1102 return if !scalar(keys %{$conf->{pending
}});
1104 my $running = PVE
::QemuServer
::check_running
($vmid);
1106 # apply pending changes
1108 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1112 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1113 raise_param_exc
($errors) if scalar(keys %$errors);
1115 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1125 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1127 if ($background_delay) {
1129 # Note: It would be better to do that in the Event based HTTPServer
1130 # to avoid blocking call to sleep.
1132 my $end_time = time() + $background_delay;
1134 my $task = PVE
::Tools
::upid_decode
($upid);
1137 while (time() < $end_time) {
1138 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1140 sleep(1); # this gets interrupted when child process ends
1144 my $status = PVE
::Tools
::upid_read_status
($upid);
1145 return undef if $status eq 'OK';
1154 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1157 my $vm_config_perm_list = [
1162 'VM.Config.Network',
1164 'VM.Config.Options',
1167 __PACKAGE__-
>register_method({
1168 name
=> 'update_vm_async',
1169 path
=> '{vmid}/config',
1173 description
=> "Set virtual machine options (asynchrounous API).",
1175 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1178 additionalProperties
=> 0,
1179 properties
=> PVE
::QemuServer
::json_config_properties
(
1181 node
=> get_standard_option
('pve-node'),
1182 vmid
=> get_standard_option
('pve-vmid'),
1183 skiplock
=> get_standard_option
('skiplock'),
1185 type
=> 'string', format
=> 'pve-configid-list',
1186 description
=> "A list of settings you want to delete.",
1190 type
=> 'string', format
=> 'pve-configid-list',
1191 description
=> "Revert a pending change.",
1196 description
=> $opt_force_description,
1198 requires
=> 'delete',
1202 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1206 background_delay
=> {
1208 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1219 code
=> $update_vm_api,
1222 __PACKAGE__-
>register_method({
1223 name
=> 'update_vm',
1224 path
=> '{vmid}/config',
1228 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1230 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1233 additionalProperties
=> 0,
1234 properties
=> PVE
::QemuServer
::json_config_properties
(
1236 node
=> get_standard_option
('pve-node'),
1237 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1238 skiplock
=> get_standard_option
('skiplock'),
1240 type
=> 'string', format
=> 'pve-configid-list',
1241 description
=> "A list of settings you want to delete.",
1245 type
=> 'string', format
=> 'pve-configid-list',
1246 description
=> "Revert a pending change.",
1251 description
=> $opt_force_description,
1253 requires
=> 'delete',
1257 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1263 returns
=> { type
=> 'null' },
1266 &$update_vm_api($param, 1);
1272 __PACKAGE__-
>register_method({
1273 name
=> 'destroy_vm',
1278 description
=> "Destroy the vm (also delete all used/owned volumes).",
1280 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1283 additionalProperties
=> 0,
1285 node
=> get_standard_option
('pve-node'),
1286 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1287 skiplock
=> get_standard_option
('skiplock'),
1296 my $rpcenv = PVE
::RPCEnvironment
::get
();
1298 my $authuser = $rpcenv->get_user();
1300 my $vmid = $param->{vmid
};
1302 my $skiplock = $param->{skiplock
};
1303 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1304 if $skiplock && $authuser ne 'root@pam';
1307 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1309 my $storecfg = PVE
::Storage
::config
();
1311 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1313 die "unable to remove VM $vmid - used in HA resources\n"
1314 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1316 # do not allow destroy if there are replication jobs
1317 my $repl_conf = PVE
::ReplicationConfig-
>new();
1318 $repl_conf->check_for_existing_jobs($vmid);
1320 # early tests (repeat after locking)
1321 die "VM $vmid is running - destroy failed\n"
1322 if PVE
::QemuServer
::check_running
($vmid);
1327 syslog
('info', "destroy VM $vmid: $upid\n");
1329 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1331 PVE
::AccessControl
::remove_vm_access
($vmid);
1333 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1336 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1339 __PACKAGE__-
>register_method({
1341 path
=> '{vmid}/unlink',
1345 description
=> "Unlink/delete disk images.",
1347 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1350 additionalProperties
=> 0,
1352 node
=> get_standard_option
('pve-node'),
1353 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1355 type
=> 'string', format
=> 'pve-configid-list',
1356 description
=> "A list of disk IDs you want to delete.",
1360 description
=> $opt_force_description,
1365 returns
=> { type
=> 'null'},
1369 $param->{delete} = extract_param
($param, 'idlist');
1371 __PACKAGE__-
>update_vm($param);
1378 __PACKAGE__-
>register_method({
1380 path
=> '{vmid}/vncproxy',
1384 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1386 description
=> "Creates a TCP VNC proxy connections.",
1388 additionalProperties
=> 0,
1390 node
=> get_standard_option
('pve-node'),
1391 vmid
=> get_standard_option
('pve-vmid'),
1395 description
=> "starts websockify instead of vncproxy",
1400 additionalProperties
=> 0,
1402 user
=> { type
=> 'string' },
1403 ticket
=> { type
=> 'string' },
1404 cert
=> { type
=> 'string' },
1405 port
=> { type
=> 'integer' },
1406 upid
=> { type
=> 'string' },
1412 my $rpcenv = PVE
::RPCEnvironment
::get
();
1414 my $authuser = $rpcenv->get_user();
1416 my $vmid = $param->{vmid
};
1417 my $node = $param->{node
};
1418 my $websocket = $param->{websocket
};
1420 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1422 my $authpath = "/vms/$vmid";
1424 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1426 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1429 my ($remip, $family);
1432 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1433 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1434 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1435 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1437 $family = PVE
::Tools
::get_host_address_family
($node);
1440 my $port = PVE
::Tools
::next_vnc_port
($family);
1447 syslog
('info', "starting vnc proxy $upid\n");
1451 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1453 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1455 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1456 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1457 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1458 '-timeout', $timeout, '-authpath', $authpath,
1459 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1460 PVE
::Tools
::run_command
($cmd);
1463 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1465 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1467 my $sock = IO
::Socket
::IP-
>new(
1472 GetAddrInfoFlags
=> 0,
1473 ) or die "failed to create socket: $!\n";
1474 # Inside the worker we shouldn't have any previous alarms
1475 # running anyway...:
1477 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1479 accept(my $cli, $sock) or die "connection failed: $!\n";
1482 if (PVE
::Tools
::run_command
($cmd,
1483 output
=> '>&'.fileno($cli),
1484 input
=> '<&'.fileno($cli),
1487 die "Failed to run vncproxy.\n";
1494 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1496 PVE
::Tools
::wait_for_vnc_port
($port);
1507 __PACKAGE__-
>register_method({
1508 name
=> 'vncwebsocket',
1509 path
=> '{vmid}/vncwebsocket',
1512 description
=> "You also need to pass a valid ticket (vncticket).",
1513 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1515 description
=> "Opens a weksocket for VNC traffic.",
1517 additionalProperties
=> 0,
1519 node
=> get_standard_option
('pve-node'),
1520 vmid
=> get_standard_option
('pve-vmid'),
1522 description
=> "Ticket from previous call to vncproxy.",
1527 description
=> "Port number returned by previous vncproxy call.",
1537 port
=> { type
=> 'string' },
1543 my $rpcenv = PVE
::RPCEnvironment
::get
();
1545 my $authuser = $rpcenv->get_user();
1547 my $vmid = $param->{vmid
};
1548 my $node = $param->{node
};
1550 my $authpath = "/vms/$vmid";
1552 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1554 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1556 # Note: VNC ports are acessible from outside, so we do not gain any
1557 # security if we verify that $param->{port} belongs to VM $vmid. This
1558 # check is done by verifying the VNC ticket (inside VNC protocol).
1560 my $port = $param->{port
};
1562 return { port
=> $port };
1565 __PACKAGE__-
>register_method({
1566 name
=> 'spiceproxy',
1567 path
=> '{vmid}/spiceproxy',
1572 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1574 description
=> "Returns a SPICE configuration to connect to the VM.",
1576 additionalProperties
=> 0,
1578 node
=> get_standard_option
('pve-node'),
1579 vmid
=> get_standard_option
('pve-vmid'),
1580 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1583 returns
=> get_standard_option
('remote-viewer-config'),
1587 my $rpcenv = PVE
::RPCEnvironment
::get
();
1589 my $authuser = $rpcenv->get_user();
1591 my $vmid = $param->{vmid
};
1592 my $node = $param->{node
};
1593 my $proxy = $param->{proxy
};
1595 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1596 my $title = "VM $vmid";
1597 $title .= " - ". $conf->{name
} if $conf->{name
};
1599 my $port = PVE
::QemuServer
::spice_port
($vmid);
1601 my ($ticket, undef, $remote_viewer_config) =
1602 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1604 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1605 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1607 return $remote_viewer_config;
1610 __PACKAGE__-
>register_method({
1612 path
=> '{vmid}/status',
1615 description
=> "Directory index",
1620 additionalProperties
=> 0,
1622 node
=> get_standard_option
('pve-node'),
1623 vmid
=> get_standard_option
('pve-vmid'),
1631 subdir
=> { type
=> 'string' },
1634 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1640 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1643 { subdir
=> 'current' },
1644 { subdir
=> 'start' },
1645 { subdir
=> 'stop' },
1651 __PACKAGE__-
>register_method({
1652 name
=> 'vm_status',
1653 path
=> '{vmid}/status/current',
1656 protected
=> 1, # qemu pid files are only readable by root
1657 description
=> "Get virtual machine status.",
1659 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1662 additionalProperties
=> 0,
1664 node
=> get_standard_option
('pve-node'),
1665 vmid
=> get_standard_option
('pve-vmid'),
1668 returns
=> { type
=> 'object' },
1673 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1675 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1676 my $status = $vmstatus->{$param->{vmid
}};
1678 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1680 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1685 __PACKAGE__-
>register_method({
1687 path
=> '{vmid}/status/start',
1691 description
=> "Start virtual machine.",
1693 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1696 additionalProperties
=> 0,
1698 node
=> get_standard_option
('pve-node'),
1699 vmid
=> get_standard_option
('pve-vmid',
1700 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1701 skiplock
=> get_standard_option
('skiplock'),
1702 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1703 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1706 enum
=> ['secure', 'insecure'],
1707 description
=> "Migration traffic is encrypted using an SSH " .
1708 "tunnel by default. On secure, completely private networks " .
1709 "this can be disabled to increase performance.",
1712 migration_network
=> {
1713 type
=> 'string', format
=> 'CIDR',
1714 description
=> "CIDR of the (sub) network that is used for migration.",
1717 machine
=> get_standard_option
('pve-qm-machine'),
1719 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1731 my $rpcenv = PVE
::RPCEnvironment
::get
();
1733 my $authuser = $rpcenv->get_user();
1735 my $node = extract_param
($param, 'node');
1737 my $vmid = extract_param
($param, 'vmid');
1739 my $machine = extract_param
($param, 'machine');
1741 my $stateuri = extract_param
($param, 'stateuri');
1742 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1743 if $stateuri && $authuser ne 'root@pam';
1745 my $skiplock = extract_param
($param, 'skiplock');
1746 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1747 if $skiplock && $authuser ne 'root@pam';
1749 my $migratedfrom = extract_param
($param, 'migratedfrom');
1750 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1751 if $migratedfrom && $authuser ne 'root@pam';
1753 my $migration_type = extract_param
($param, 'migration_type');
1754 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1755 if $migration_type && $authuser ne 'root@pam';
1757 my $migration_network = extract_param
($param, 'migration_network');
1758 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1759 if $migration_network && $authuser ne 'root@pam';
1761 my $targetstorage = extract_param
($param, 'targetstorage');
1762 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1763 if $targetstorage && $authuser ne 'root@pam';
1765 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1766 if $targetstorage && !$migratedfrom;
1768 # read spice ticket from STDIN
1770 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1771 if (defined(my $line = <>)) {
1773 $spice_ticket = $line;
1777 PVE
::Cluster
::check_cfs_quorum
();
1779 my $storecfg = PVE
::Storage
::config
();
1781 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1782 $rpcenv->{type
} ne 'ha') {
1787 my $service = "vm:$vmid";
1789 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1791 print "Requesting HA start for VM $vmid\n";
1793 PVE
::Tools
::run_command
($cmd);
1798 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1805 syslog
('info', "start VM $vmid: $upid\n");
1807 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1808 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1813 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1817 __PACKAGE__-
>register_method({
1819 path
=> '{vmid}/status/stop',
1823 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1824 "is akin to pulling the power plug of a running computer and may damage the VM data",
1826 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1829 additionalProperties
=> 0,
1831 node
=> get_standard_option
('pve-node'),
1832 vmid
=> get_standard_option
('pve-vmid',
1833 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1834 skiplock
=> get_standard_option
('skiplock'),
1835 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1837 description
=> "Wait maximal timeout seconds.",
1843 description
=> "Do not deactivate storage volumes.",
1856 my $rpcenv = PVE
::RPCEnvironment
::get
();
1858 my $authuser = $rpcenv->get_user();
1860 my $node = extract_param
($param, 'node');
1862 my $vmid = extract_param
($param, 'vmid');
1864 my $skiplock = extract_param
($param, 'skiplock');
1865 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1866 if $skiplock && $authuser ne 'root@pam';
1868 my $keepActive = extract_param
($param, 'keepActive');
1869 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1870 if $keepActive && $authuser ne 'root@pam';
1872 my $migratedfrom = extract_param
($param, 'migratedfrom');
1873 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1874 if $migratedfrom && $authuser ne 'root@pam';
1877 my $storecfg = PVE
::Storage
::config
();
1879 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1884 my $service = "vm:$vmid";
1886 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1888 print "Requesting HA stop for VM $vmid\n";
1890 PVE
::Tools
::run_command
($cmd);
1895 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1901 syslog
('info', "stop VM $vmid: $upid\n");
1903 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1904 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1909 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1913 __PACKAGE__-
>register_method({
1915 path
=> '{vmid}/status/reset',
1919 description
=> "Reset virtual machine.",
1921 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1924 additionalProperties
=> 0,
1926 node
=> get_standard_option
('pve-node'),
1927 vmid
=> get_standard_option
('pve-vmid',
1928 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1929 skiplock
=> get_standard_option
('skiplock'),
1938 my $rpcenv = PVE
::RPCEnvironment
::get
();
1940 my $authuser = $rpcenv->get_user();
1942 my $node = extract_param
($param, 'node');
1944 my $vmid = extract_param
($param, 'vmid');
1946 my $skiplock = extract_param
($param, 'skiplock');
1947 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1948 if $skiplock && $authuser ne 'root@pam';
1950 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1955 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1960 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1963 __PACKAGE__-
>register_method({
1964 name
=> 'vm_shutdown',
1965 path
=> '{vmid}/status/shutdown',
1969 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1970 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1972 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1975 additionalProperties
=> 0,
1977 node
=> get_standard_option
('pve-node'),
1978 vmid
=> get_standard_option
('pve-vmid',
1979 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1980 skiplock
=> get_standard_option
('skiplock'),
1982 description
=> "Wait maximal timeout seconds.",
1988 description
=> "Make sure the VM stops.",
1994 description
=> "Do not deactivate storage volumes.",
2007 my $rpcenv = PVE
::RPCEnvironment
::get
();
2009 my $authuser = $rpcenv->get_user();
2011 my $node = extract_param
($param, 'node');
2013 my $vmid = extract_param
($param, 'vmid');
2015 my $skiplock = extract_param
($param, 'skiplock');
2016 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2017 if $skiplock && $authuser ne 'root@pam';
2019 my $keepActive = extract_param
($param, 'keepActive');
2020 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2021 if $keepActive && $authuser ne 'root@pam';
2023 my $storecfg = PVE
::Storage
::config
();
2027 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2028 # otherwise, we will infer a shutdown command, but run into the timeout,
2029 # then when the vm is resumed, it will instantly shutdown
2031 # checking the qmp status here to get feedback to the gui/cli/api
2032 # and the status query should not take too long
2035 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2039 if (!$err && $qmpstatus->{status
} eq "paused") {
2040 if ($param->{forceStop
}) {
2041 warn "VM is paused - stop instead of shutdown\n";
2044 die "VM is paused - cannot shutdown\n";
2048 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2049 ($rpcenv->{type
} ne 'ha')) {
2054 my $service = "vm:$vmid";
2056 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2058 print "Requesting HA stop for VM $vmid\n";
2060 PVE
::Tools
::run_command
($cmd);
2065 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2072 syslog
('info', "shutdown VM $vmid: $upid\n");
2074 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2075 $shutdown, $param->{forceStop
}, $keepActive);
2080 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2084 __PACKAGE__-
>register_method({
2085 name
=> 'vm_suspend',
2086 path
=> '{vmid}/status/suspend',
2090 description
=> "Suspend virtual machine.",
2092 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2095 additionalProperties
=> 0,
2097 node
=> get_standard_option
('pve-node'),
2098 vmid
=> get_standard_option
('pve-vmid',
2099 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2100 skiplock
=> get_standard_option
('skiplock'),
2109 my $rpcenv = PVE
::RPCEnvironment
::get
();
2111 my $authuser = $rpcenv->get_user();
2113 my $node = extract_param
($param, 'node');
2115 my $vmid = extract_param
($param, 'vmid');
2117 my $skiplock = extract_param
($param, 'skiplock');
2118 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2119 if $skiplock && $authuser ne 'root@pam';
2121 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2126 syslog
('info', "suspend VM $vmid: $upid\n");
2128 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2133 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2136 __PACKAGE__-
>register_method({
2137 name
=> 'vm_resume',
2138 path
=> '{vmid}/status/resume',
2142 description
=> "Resume virtual machine.",
2144 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2147 additionalProperties
=> 0,
2149 node
=> get_standard_option
('pve-node'),
2150 vmid
=> get_standard_option
('pve-vmid',
2151 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2152 skiplock
=> get_standard_option
('skiplock'),
2153 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2163 my $rpcenv = PVE
::RPCEnvironment
::get
();
2165 my $authuser = $rpcenv->get_user();
2167 my $node = extract_param
($param, 'node');
2169 my $vmid = extract_param
($param, 'vmid');
2171 my $skiplock = extract_param
($param, 'skiplock');
2172 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2173 if $skiplock && $authuser ne 'root@pam';
2175 my $nocheck = extract_param
($param, 'nocheck');
2177 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2182 syslog
('info', "resume VM $vmid: $upid\n");
2184 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2189 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2192 __PACKAGE__-
>register_method({
2193 name
=> 'vm_sendkey',
2194 path
=> '{vmid}/sendkey',
2198 description
=> "Send key event to virtual machine.",
2200 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2203 additionalProperties
=> 0,
2205 node
=> get_standard_option
('pve-node'),
2206 vmid
=> get_standard_option
('pve-vmid',
2207 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2208 skiplock
=> get_standard_option
('skiplock'),
2210 description
=> "The key (qemu monitor encoding).",
2215 returns
=> { type
=> 'null'},
2219 my $rpcenv = PVE
::RPCEnvironment
::get
();
2221 my $authuser = $rpcenv->get_user();
2223 my $node = extract_param
($param, 'node');
2225 my $vmid = extract_param
($param, 'vmid');
2227 my $skiplock = extract_param
($param, 'skiplock');
2228 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2229 if $skiplock && $authuser ne 'root@pam';
2231 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2236 __PACKAGE__-
>register_method({
2237 name
=> 'vm_feature',
2238 path
=> '{vmid}/feature',
2242 description
=> "Check if feature for virtual machine is available.",
2244 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2247 additionalProperties
=> 0,
2249 node
=> get_standard_option
('pve-node'),
2250 vmid
=> get_standard_option
('pve-vmid'),
2252 description
=> "Feature to check.",
2254 enum
=> [ 'snapshot', 'clone', 'copy' ],
2256 snapname
=> get_standard_option
('pve-snapshot-name', {
2264 hasFeature
=> { type
=> 'boolean' },
2267 items
=> { type
=> 'string' },
2274 my $node = extract_param
($param, 'node');
2276 my $vmid = extract_param
($param, 'vmid');
2278 my $snapname = extract_param
($param, 'snapname');
2280 my $feature = extract_param
($param, 'feature');
2282 my $running = PVE
::QemuServer
::check_running
($vmid);
2284 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2287 my $snap = $conf->{snapshots
}->{$snapname};
2288 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2291 my $storecfg = PVE
::Storage
::config
();
2293 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2294 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2297 hasFeature
=> $hasFeature,
2298 nodes
=> [ keys %$nodelist ],
2302 __PACKAGE__-
>register_method({
2304 path
=> '{vmid}/clone',
2308 description
=> "Create a copy of virtual machine/template.",
2310 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2311 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2312 "'Datastore.AllocateSpace' on any used storage.",
2315 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2317 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2318 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2323 additionalProperties
=> 0,
2325 node
=> get_standard_option
('pve-node'),
2326 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2327 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2330 type
=> 'string', format
=> 'dns-name',
2331 description
=> "Set a name for the new VM.",
2336 description
=> "Description for the new VM.",
2340 type
=> 'string', format
=> 'pve-poolid',
2341 description
=> "Add the new VM to the specified pool.",
2343 snapname
=> get_standard_option
('pve-snapshot-name', {
2346 storage
=> get_standard_option
('pve-storage-id', {
2347 description
=> "Target storage for full clone.",
2352 description
=> "Target format for file storage.",
2356 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2361 description
=> "Create a full copy of all disk. This is always done when " .
2362 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2365 target
=> get_standard_option
('pve-node', {
2366 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2377 my $rpcenv = PVE
::RPCEnvironment
::get
();
2379 my $authuser = $rpcenv->get_user();
2381 my $node = extract_param
($param, 'node');
2383 my $vmid = extract_param
($param, 'vmid');
2385 my $newid = extract_param
($param, 'newid');
2387 my $pool = extract_param
($param, 'pool');
2389 if (defined($pool)) {
2390 $rpcenv->check_pool_exist($pool);
2393 my $snapname = extract_param
($param, 'snapname');
2395 my $storage = extract_param
($param, 'storage');
2397 my $format = extract_param
($param, 'format');
2399 my $target = extract_param
($param, 'target');
2401 my $localnode = PVE
::INotify
::nodename
();
2403 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2405 PVE
::Cluster
::check_node_exists
($target) if $target;
2407 my $storecfg = PVE
::Storage
::config
();
2410 # check if storage is enabled on local node
2411 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2413 # check if storage is available on target node
2414 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2415 # clone only works if target storage is shared
2416 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2417 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2421 PVE
::Cluster
::check_cfs_quorum
();
2423 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2425 # exclusive lock if VM is running - else shared lock is enough;
2426 my $shared_lock = $running ?
0 : 1;
2430 # do all tests after lock
2431 # we also try to do all tests before we fork the worker
2433 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2435 PVE
::QemuConfig-
>check_lock($conf);
2437 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2439 die "unexpected state change\n" if $verify_running != $running;
2441 die "snapshot '$snapname' does not exist\n"
2442 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2444 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2446 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2448 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2450 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2452 die "unable to create VM $newid: config file already exists\n"
2455 my $newconf = { lock => 'clone' };
2460 foreach my $opt (keys %$oldconf) {
2461 my $value = $oldconf->{$opt};
2463 # do not copy snapshot related info
2464 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2465 $opt eq 'vmstate' || $opt eq 'snapstate';
2467 # no need to copy unused images, because VMID(owner) changes anyways
2468 next if $opt =~ m/^unused\d+$/;
2470 # always change MAC! address
2471 if ($opt =~ m/^net(\d+)$/) {
2472 my $net = PVE
::QemuServer
::parse_net
($value);
2473 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2474 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2475 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2476 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2477 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2478 die "unable to parse drive options for '$opt'\n" if !$drive;
2479 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2480 $newconf->{$opt} = $value; # simply copy configuration
2482 if ($param->{full
}) {
2483 die "Full clone feature is not supported for drive '$opt'\n"
2484 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2485 $fullclone->{$opt} = 1;
2487 # not full means clone instead of copy
2488 die "Linked clone feature is not supported for drive '$opt'\n"
2489 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2491 $drives->{$opt} = $drive;
2492 push @$vollist, $drive->{file
};
2495 # copy everything else
2496 $newconf->{$opt} = $value;
2500 # auto generate a new uuid
2501 my ($uuid, $uuid_str);
2502 UUID
::generate
($uuid);
2503 UUID
::unparse
($uuid, $uuid_str);
2504 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2505 $smbios1->{uuid
} = $uuid_str;
2506 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2508 delete $newconf->{template
};
2510 if ($param->{name
}) {
2511 $newconf->{name
} = $param->{name
};
2513 if ($oldconf->{name
}) {
2514 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2516 $newconf->{name
} = "Copy-of-VM-$vmid";
2520 if ($param->{description
}) {
2521 $newconf->{description
} = $param->{description
};
2524 # create empty/temp config - this fails if VM already exists on other node
2525 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2530 my $newvollist = [];
2537 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2539 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2541 my $total_jobs = scalar(keys %{$drives});
2544 foreach my $opt (keys %$drives) {
2545 my $drive = $drives->{$opt};
2546 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2548 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2549 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2550 $jobs, $skipcomplete, $oldconf->{agent
});
2552 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2554 PVE
::QemuConfig-
>write_config($newid, $newconf);
2558 delete $newconf->{lock};
2559 PVE
::QemuConfig-
>write_config($newid, $newconf);
2562 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2563 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2564 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2566 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2567 die "Failed to move config to node '$target' - rename failed: $!\n"
2568 if !rename($conffile, $newconffile);
2571 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2576 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2578 sleep 1; # some storage like rbd need to wait before release volume - really?
2580 foreach my $volid (@$newvollist) {
2581 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2584 die "clone failed: $err";
2590 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2592 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2595 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2596 # Aquire exclusive lock lock for $newid
2597 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2602 __PACKAGE__-
>register_method({
2603 name
=> 'move_vm_disk',
2604 path
=> '{vmid}/move_disk',
2608 description
=> "Move volume to different storage.",
2610 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2612 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2613 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2617 additionalProperties
=> 0,
2619 node
=> get_standard_option
('pve-node'),
2620 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2623 description
=> "The disk you want to move.",
2624 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2626 storage
=> get_standard_option
('pve-storage-id', {
2627 description
=> "Target storage.",
2628 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2632 description
=> "Target Format.",
2633 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2638 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2644 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2652 description
=> "the task ID.",
2657 my $rpcenv = PVE
::RPCEnvironment
::get
();
2659 my $authuser = $rpcenv->get_user();
2661 my $node = extract_param
($param, 'node');
2663 my $vmid = extract_param
($param, 'vmid');
2665 my $digest = extract_param
($param, 'digest');
2667 my $disk = extract_param
($param, 'disk');
2669 my $storeid = extract_param
($param, 'storage');
2671 my $format = extract_param
($param, 'format');
2673 my $storecfg = PVE
::Storage
::config
();
2675 my $updatefn = sub {
2677 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2679 PVE
::QemuConfig-
>check_lock($conf);
2681 die "checksum missmatch (file change by other user?)\n"
2682 if $digest && $digest ne $conf->{digest
};
2684 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2686 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2688 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2690 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2693 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2694 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2698 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2699 (!$format || !$oldfmt || $oldfmt eq $format);
2701 # this only checks snapshots because $disk is passed!
2702 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2703 die "you can't move a disk with snapshots and delete the source\n"
2704 if $snapshotted && $param->{delete};
2706 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2708 my $running = PVE
::QemuServer
::check_running
($vmid);
2710 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2714 my $newvollist = [];
2717 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2719 warn "moving disk with snapshots, snapshots will not be moved!\n"
2722 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2723 $vmid, $storeid, $format, 1, $newvollist);
2725 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2727 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2729 # convert moved disk to base if part of template
2730 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2731 if PVE
::QemuConfig-
>is_template($conf);
2733 PVE
::QemuConfig-
>write_config($vmid, $conf);
2736 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2737 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2744 foreach my $volid (@$newvollist) {
2745 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2748 die "storage migration failed: $err";
2751 if ($param->{delete}) {
2753 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2754 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2760 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2763 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2766 __PACKAGE__-
>register_method({
2767 name
=> 'migrate_vm',
2768 path
=> '{vmid}/migrate',
2772 description
=> "Migrate virtual machine. Creates a new migration task.",
2774 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2777 additionalProperties
=> 0,
2779 node
=> get_standard_option
('pve-node'),
2780 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2781 target
=> get_standard_option
('pve-node', {
2782 description
=> "Target node.",
2783 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2787 description
=> "Use online/live migration.",
2792 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2797 enum
=> ['secure', 'insecure'],
2798 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2801 migration_network
=> {
2802 type
=> 'string', format
=> 'CIDR',
2803 description
=> "CIDR of the (sub) network that is used for migration.",
2806 "with-local-disks" => {
2808 description
=> "Enable live storage migration for local disk",
2811 targetstorage
=> get_standard_option
('pve-storage-id', {
2812 description
=> "Default target storage.",
2814 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2820 description
=> "the task ID.",
2825 my $rpcenv = PVE
::RPCEnvironment
::get
();
2827 my $authuser = $rpcenv->get_user();
2829 my $target = extract_param
($param, 'target');
2831 my $localnode = PVE
::INotify
::nodename
();
2832 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2834 PVE
::Cluster
::check_cfs_quorum
();
2836 PVE
::Cluster
::check_node_exists
($target);
2838 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2840 my $vmid = extract_param
($param, 'vmid');
2842 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2843 if !$param->{online
} && $param->{targetstorage
};
2845 raise_param_exc
({ force
=> "Only root may use this option." })
2846 if $param->{force
} && $authuser ne 'root@pam';
2848 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2849 if $param->{migration_type
} && $authuser ne 'root@pam';
2851 # allow root only until better network permissions are available
2852 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2853 if $param->{migration_network
} && $authuser ne 'root@pam';
2856 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2858 # try to detect errors early
2860 PVE
::QemuConfig-
>check_lock($conf);
2862 if (PVE
::QemuServer
::check_running
($vmid)) {
2863 die "cant migrate running VM without --online\n"
2864 if !$param->{online
};
2867 my $storecfg = PVE
::Storage
::config
();
2869 if( $param->{targetstorage
}) {
2870 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2872 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2875 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2880 my $service = "vm:$vmid";
2882 my $cmd = ['ha-manager', 'migrate', $service, $target];
2884 print "Requesting HA migration for VM $vmid to node $target\n";
2886 PVE
::Tools
::run_command
($cmd);
2891 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2896 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2900 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2903 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2908 __PACKAGE__-
>register_method({
2910 path
=> '{vmid}/monitor',
2914 description
=> "Execute Qemu monitor commands.",
2916 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2920 additionalProperties
=> 0,
2922 node
=> get_standard_option
('pve-node'),
2923 vmid
=> get_standard_option
('pve-vmid'),
2926 description
=> "The monitor command.",
2930 returns
=> { type
=> 'string'},
2934 my $rpcenv = PVE
::RPCEnvironment
::get
();
2935 my $authuser = $rpcenv->get_user();
2938 my $command = shift;
2939 return $command =~ m/^\s*info(\s+|$)/
2940 || $command =~ m/^\s*help\s*$/;
2943 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2944 if !&$is_ro($param->{command
});
2946 my $vmid = $param->{vmid
};
2948 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2952 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2954 $res = "ERROR: $@" if $@;
2959 my $guest_agent_commands = [
2967 'network-get-interfaces',
2970 'get-memory-blocks',
2971 'get-memory-block-info',
2978 __PACKAGE__-
>register_method({
2980 path
=> '{vmid}/agent',
2984 description
=> "Execute Qemu Guest Agent commands.",
2986 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2989 additionalProperties
=> 0,
2991 node
=> get_standard_option
('pve-node'),
2992 vmid
=> get_standard_option
('pve-vmid', {
2993 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2996 description
=> "The QGA command.",
2997 enum
=> $guest_agent_commands,
3003 description
=> "Returns an object with a single `result` property. The type of that
3004 property depends on the executed command.",
3009 my $vmid = $param->{vmid
};
3011 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3013 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3014 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3016 my $cmd = $param->{command
};
3018 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3020 return { result
=> $res };
3023 __PACKAGE__-
>register_method({
3024 name
=> 'resize_vm',
3025 path
=> '{vmid}/resize',
3029 description
=> "Extend volume size.",
3031 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3034 additionalProperties
=> 0,
3036 node
=> get_standard_option
('pve-node'),
3037 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3038 skiplock
=> get_standard_option
('skiplock'),
3041 description
=> "The disk you want to resize.",
3042 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3046 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3047 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.",
3051 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3057 returns
=> { type
=> 'null'},
3061 my $rpcenv = PVE
::RPCEnvironment
::get
();
3063 my $authuser = $rpcenv->get_user();
3065 my $node = extract_param
($param, 'node');
3067 my $vmid = extract_param
($param, 'vmid');
3069 my $digest = extract_param
($param, 'digest');
3071 my $disk = extract_param
($param, 'disk');
3073 my $sizestr = extract_param
($param, 'size');
3075 my $skiplock = extract_param
($param, 'skiplock');
3076 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3077 if $skiplock && $authuser ne 'root@pam';
3079 my $storecfg = PVE
::Storage
::config
();
3081 my $updatefn = sub {
3083 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3085 die "checksum missmatch (file change by other user?)\n"
3086 if $digest && $digest ne $conf->{digest
};
3087 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3089 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3091 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3093 my (undef, undef, undef, undef, undef, undef, $format) =
3094 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3096 die "can't resize volume: $disk if snapshot exists\n"
3097 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3099 my $volid = $drive->{file
};
3101 die "disk '$disk' has no associated volume\n" if !$volid;
3103 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3105 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3107 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3109 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3110 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3112 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3113 my ($ext, $newsize, $unit) = ($1, $2, $4);
3116 $newsize = $newsize * 1024;
3117 } elsif ($unit eq 'M') {
3118 $newsize = $newsize * 1024 * 1024;
3119 } elsif ($unit eq 'G') {
3120 $newsize = $newsize * 1024 * 1024 * 1024;
3121 } elsif ($unit eq 'T') {
3122 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3125 $newsize += $size if $ext;
3126 $newsize = int($newsize);
3128 die "shrinking disks is not supported\n" if $newsize < $size;
3130 return if $size == $newsize;
3132 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3134 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3136 $drive->{size
} = $newsize;
3137 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3139 PVE
::QemuConfig-
>write_config($vmid, $conf);
3142 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3146 __PACKAGE__-
>register_method({
3147 name
=> 'snapshot_list',
3148 path
=> '{vmid}/snapshot',
3150 description
=> "List all snapshots.",
3152 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3155 protected
=> 1, # qemu pid files are only readable by root
3157 additionalProperties
=> 0,
3159 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3160 node
=> get_standard_option
('pve-node'),
3169 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3174 my $vmid = $param->{vmid
};
3176 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3177 my $snaphash = $conf->{snapshots
} || {};
3181 foreach my $name (keys %$snaphash) {
3182 my $d = $snaphash->{$name};
3185 snaptime
=> $d->{snaptime
} || 0,
3186 vmstate
=> $d->{vmstate
} ?
1 : 0,
3187 description
=> $d->{description
} || '',
3189 $item->{parent
} = $d->{parent
} if $d->{parent
};
3190 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3194 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3195 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3196 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3198 push @$res, $current;
3203 __PACKAGE__-
>register_method({
3205 path
=> '{vmid}/snapshot',
3209 description
=> "Snapshot a VM.",
3211 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3214 additionalProperties
=> 0,
3216 node
=> get_standard_option
('pve-node'),
3217 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3218 snapname
=> get_standard_option
('pve-snapshot-name'),
3222 description
=> "Save the vmstate",
3227 description
=> "A textual description or comment.",
3233 description
=> "the task ID.",
3238 my $rpcenv = PVE
::RPCEnvironment
::get
();
3240 my $authuser = $rpcenv->get_user();
3242 my $node = extract_param
($param, 'node');
3244 my $vmid = extract_param
($param, 'vmid');
3246 my $snapname = extract_param
($param, 'snapname');
3248 die "unable to use snapshot name 'current' (reserved name)\n"
3249 if $snapname eq 'current';
3252 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3253 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3254 $param->{description
});
3257 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3260 __PACKAGE__-
>register_method({
3261 name
=> 'snapshot_cmd_idx',
3262 path
=> '{vmid}/snapshot/{snapname}',
3269 additionalProperties
=> 0,
3271 vmid
=> get_standard_option
('pve-vmid'),
3272 node
=> get_standard_option
('pve-node'),
3273 snapname
=> get_standard_option
('pve-snapshot-name'),
3282 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3289 push @$res, { cmd
=> 'rollback' };
3290 push @$res, { cmd
=> 'config' };
3295 __PACKAGE__-
>register_method({
3296 name
=> 'update_snapshot_config',
3297 path
=> '{vmid}/snapshot/{snapname}/config',
3301 description
=> "Update snapshot metadata.",
3303 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3306 additionalProperties
=> 0,
3308 node
=> get_standard_option
('pve-node'),
3309 vmid
=> get_standard_option
('pve-vmid'),
3310 snapname
=> get_standard_option
('pve-snapshot-name'),
3314 description
=> "A textual description or comment.",
3318 returns
=> { type
=> 'null' },
3322 my $rpcenv = PVE
::RPCEnvironment
::get
();
3324 my $authuser = $rpcenv->get_user();
3326 my $vmid = extract_param
($param, 'vmid');
3328 my $snapname = extract_param
($param, 'snapname');
3330 return undef if !defined($param->{description
});
3332 my $updatefn = sub {
3334 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3336 PVE
::QemuConfig-
>check_lock($conf);
3338 my $snap = $conf->{snapshots
}->{$snapname};
3340 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3342 $snap->{description
} = $param->{description
} if defined($param->{description
});
3344 PVE
::QemuConfig-
>write_config($vmid, $conf);
3347 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3352 __PACKAGE__-
>register_method({
3353 name
=> 'get_snapshot_config',
3354 path
=> '{vmid}/snapshot/{snapname}/config',
3357 description
=> "Get snapshot configuration",
3359 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3362 additionalProperties
=> 0,
3364 node
=> get_standard_option
('pve-node'),
3365 vmid
=> get_standard_option
('pve-vmid'),
3366 snapname
=> get_standard_option
('pve-snapshot-name'),
3369 returns
=> { type
=> "object" },
3373 my $rpcenv = PVE
::RPCEnvironment
::get
();
3375 my $authuser = $rpcenv->get_user();
3377 my $vmid = extract_param
($param, 'vmid');
3379 my $snapname = extract_param
($param, 'snapname');
3381 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3383 my $snap = $conf->{snapshots
}->{$snapname};
3385 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3390 __PACKAGE__-
>register_method({
3392 path
=> '{vmid}/snapshot/{snapname}/rollback',
3396 description
=> "Rollback VM state to specified snapshot.",
3398 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3401 additionalProperties
=> 0,
3403 node
=> get_standard_option
('pve-node'),
3404 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3405 snapname
=> get_standard_option
('pve-snapshot-name'),
3410 description
=> "the task ID.",
3415 my $rpcenv = PVE
::RPCEnvironment
::get
();
3417 my $authuser = $rpcenv->get_user();
3419 my $node = extract_param
($param, 'node');
3421 my $vmid = extract_param
($param, 'vmid');
3423 my $snapname = extract_param
($param, 'snapname');
3426 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3427 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3431 # hold migration lock, this makes sure that nobody create replication snapshots
3432 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3435 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3438 __PACKAGE__-
>register_method({
3439 name
=> 'delsnapshot',
3440 path
=> '{vmid}/snapshot/{snapname}',
3444 description
=> "Delete a VM snapshot.",
3446 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3449 additionalProperties
=> 0,
3451 node
=> get_standard_option
('pve-node'),
3452 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3453 snapname
=> get_standard_option
('pve-snapshot-name'),
3457 description
=> "For removal from config file, even if removing disk snapshots fails.",
3463 description
=> "the task ID.",
3468 my $rpcenv = PVE
::RPCEnvironment
::get
();
3470 my $authuser = $rpcenv->get_user();
3472 my $node = extract_param
($param, 'node');
3474 my $vmid = extract_param
($param, 'vmid');
3476 my $snapname = extract_param
($param, 'snapname');
3479 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3480 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3483 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3486 __PACKAGE__-
>register_method({
3488 path
=> '{vmid}/template',
3492 description
=> "Create a Template.",
3494 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3495 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3498 additionalProperties
=> 0,
3500 node
=> get_standard_option
('pve-node'),
3501 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3505 description
=> "If you want to convert only 1 disk to base image.",
3506 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3511 returns
=> { type
=> 'null'},
3515 my $rpcenv = PVE
::RPCEnvironment
::get
();
3517 my $authuser = $rpcenv->get_user();
3519 my $node = extract_param
($param, 'node');
3521 my $vmid = extract_param
($param, 'vmid');
3523 my $disk = extract_param
($param, 'disk');
3525 my $updatefn = sub {
3527 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3529 PVE
::QemuConfig-
>check_lock($conf);
3531 die "unable to create template, because VM contains snapshots\n"
3532 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3534 die "you can't convert a template to a template\n"
3535 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3537 die "you can't convert a VM to template if VM is running\n"
3538 if PVE
::QemuServer
::check_running
($vmid);
3541 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3544 $conf->{template
} = 1;
3545 PVE
::QemuConfig-
>write_config($vmid, $conf);
3547 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3550 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);