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;
144 if ($ds eq 'efidisk0') {
146 my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
147 die "uefi vars image not found\n" if ! -f
$ovmfvars;
148 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
150 $disk->{file
} = $volid;
151 $disk->{size
} = 128*1024;
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,
167 $fmt, undef, $size*1024*1024);
168 $disk->{file
} = $volid;
169 $disk->{size
} = $size*1024*1024*1024;
171 push @$vollist, $volid;
172 delete $disk->{format
}; # no longer needed
173 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
176 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
178 my $volid_is_new = 1;
181 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
182 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
187 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
189 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
191 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
193 die "volume $volid does not exists\n" if !$size;
195 $disk->{size
} = $size;
198 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
202 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
204 # free allocated images on error
206 syslog
('err', "VM $vmid creating disks failed");
207 foreach my $volid (@$vollist) {
208 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
214 # modify vm config if everything went well
215 foreach my $ds (keys %$res) {
216 $conf->{$ds} = $res->{$ds};
233 my $memoryoptions = {
239 my $hwtypeoptions = {
251 my $generaloptions = {
258 'migrate_downtime' => 1,
259 'migrate_speed' => 1,
271 my $vmpoweroptions = {
280 my $check_vm_modify_config_perm = sub {
281 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
283 return 1 if $authuser eq 'root@pam';
285 foreach my $opt (@$key_list) {
286 # disk checks need to be done somewhere else
287 next if PVE
::QemuServer
::is_valid_drivename
($opt);
288 next if $opt eq 'cdrom';
289 next if $opt =~ m/^unused\d+$/;
291 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
292 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
293 } elsif ($memoryoptions->{$opt}) {
294 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
295 } elsif ($hwtypeoptions->{$opt}) {
296 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
297 } elsif ($generaloptions->{$opt}) {
298 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
299 # special case for startup since it changes host behaviour
300 if ($opt eq 'startup') {
301 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
303 } elsif ($vmpoweroptions->{$opt}) {
304 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
305 } elsif ($diskoptions->{$opt}) {
306 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
307 } elsif ($opt =~ m/^net\d+$/) {
308 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
310 # catches usb\d+, hostpci\d+, args, lock, etc.
311 # new options will be checked here
312 die "only root can set '$opt' config\n";
319 __PACKAGE__-
>register_method({
323 description
=> "Virtual machine index (per node).",
325 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
329 protected
=> 1, # qemu pid files are only readable by root
331 additionalProperties
=> 0,
333 node
=> get_standard_option
('pve-node'),
337 description
=> "Determine the full status of active VMs.",
347 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
352 my $rpcenv = PVE
::RPCEnvironment
::get
();
353 my $authuser = $rpcenv->get_user();
355 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
358 foreach my $vmid (keys %$vmstatus) {
359 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
361 my $data = $vmstatus->{$vmid};
362 $data->{vmid
} = int($vmid);
371 __PACKAGE__-
>register_method({
375 description
=> "Create or restore a virtual machine.",
377 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
378 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
379 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
380 user
=> 'all', # check inside
385 additionalProperties
=> 0,
386 properties
=> PVE
::QemuServer
::json_config_properties
(
388 node
=> get_standard_option
('pve-node'),
389 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
391 description
=> "The backup file.",
395 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
397 storage
=> get_standard_option
('pve-storage-id', {
398 description
=> "Default storage.",
400 completion
=> \
&PVE
::QemuServer
::complete_storage
,
405 description
=> "Allow to overwrite existing VM.",
406 requires
=> 'archive',
411 description
=> "Assign a unique random ethernet address.",
412 requires
=> 'archive',
416 type
=> 'string', format
=> 'pve-poolid',
417 description
=> "Add the VM to the specified pool.",
427 my $rpcenv = PVE
::RPCEnvironment
::get
();
429 my $authuser = $rpcenv->get_user();
431 my $node = extract_param
($param, 'node');
433 my $vmid = extract_param
($param, 'vmid');
435 my $archive = extract_param
($param, 'archive');
437 my $storage = extract_param
($param, 'storage');
439 my $force = extract_param
($param, 'force');
441 my $unique = extract_param
($param, 'unique');
443 my $pool = extract_param
($param, 'pool');
445 my $filename = PVE
::QemuConfig-
>config_file($vmid);
447 my $storecfg = PVE
::Storage
::config
();
449 PVE
::Cluster
::check_cfs_quorum
();
451 if (defined($pool)) {
452 $rpcenv->check_pool_exist($pool);
455 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
456 if defined($storage);
458 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
460 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
462 } elsif ($archive && $force && (-f
$filename) &&
463 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
464 # OK: user has VM.Backup permissions, and want to restore an existing VM
470 &$resolve_cdrom_alias($param);
472 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
474 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
476 foreach my $opt (keys %$param) {
477 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
478 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
479 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
481 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
482 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
486 PVE
::QemuServer
::add_random_macs
($param);
488 my $keystr = join(' ', keys %$param);
489 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
491 if ($archive eq '-') {
492 die "pipe requires cli environment\n"
493 if $rpcenv->{type
} ne 'cli';
495 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
496 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
500 my $restorefn = sub {
501 my $vmlist = PVE
::Cluster
::get_vmlist
();
502 if ($vmlist->{ids
}->{$vmid}) {
503 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
504 if ($current_node eq $node) {
505 my $conf = PVE
::QemuConfig-
>load_config($vmid);
507 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
509 die "unable to restore vm $vmid - config file already exists\n"
512 die "unable to restore vm $vmid - vm is running\n"
513 if PVE
::QemuServer
::check_running
($vmid);
515 die "unable to restore vm $vmid - vm is a template\n"
516 if PVE
::QemuConfig-
>is_template($conf);
519 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
524 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
527 unique
=> $unique });
529 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
532 # ensure no old replication state are exists
533 PVE
::ReplicationState
::delete_guest_states
($vmid);
535 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
541 PVE
::Cluster
::check_vmid_unused
($vmid);
543 # ensure no old replication state are exists
544 PVE
::ReplicationState
::delete_guest_states
($vmid);
554 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
556 # try to be smart about bootdisk
557 my @disks = PVE
::QemuServer
::valid_drive_names
();
559 foreach my $ds (reverse @disks) {
560 next if !$conf->{$ds};
561 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
562 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
566 if (!$conf->{bootdisk
} && $firstdisk) {
567 $conf->{bootdisk
} = $firstdisk;
570 # auto generate uuid if user did not specify smbios1 option
571 if (!$conf->{smbios1
}) {
572 my ($uuid, $uuid_str);
573 UUID
::generate
($uuid);
574 UUID
::unparse
($uuid, $uuid_str);
575 $conf->{smbios1
} = "uuid=$uuid_str";
578 PVE
::QemuConfig-
>write_config($vmid, $conf);
584 foreach my $volid (@$vollist) {
585 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
588 die "create failed - $err";
591 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
594 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
597 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
600 __PACKAGE__-
>register_method({
605 description
=> "Directory index",
610 additionalProperties
=> 0,
612 node
=> get_standard_option
('pve-node'),
613 vmid
=> get_standard_option
('pve-vmid'),
621 subdir
=> { type
=> 'string' },
624 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
630 { subdir
=> 'config' },
631 { subdir
=> 'pending' },
632 { subdir
=> 'status' },
633 { subdir
=> 'unlink' },
634 { subdir
=> 'vncproxy' },
635 { subdir
=> 'migrate' },
636 { subdir
=> 'resize' },
637 { subdir
=> 'move' },
639 { subdir
=> 'rrddata' },
640 { subdir
=> 'monitor' },
641 { subdir
=> 'agent' },
642 { subdir
=> 'snapshot' },
643 { subdir
=> 'spiceproxy' },
644 { subdir
=> 'sendkey' },
645 { subdir
=> 'firewall' },
651 __PACKAGE__-
>register_method ({
652 subclass
=> "PVE::API2::Firewall::VM",
653 path
=> '{vmid}/firewall',
656 __PACKAGE__-
>register_method({
658 path
=> '{vmid}/rrd',
660 protected
=> 1, # fixme: can we avoid that?
662 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
664 description
=> "Read VM RRD statistics (returns PNG)",
666 additionalProperties
=> 0,
668 node
=> get_standard_option
('pve-node'),
669 vmid
=> get_standard_option
('pve-vmid'),
671 description
=> "Specify the time frame you are interested in.",
673 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
676 description
=> "The list of datasources you want to display.",
677 type
=> 'string', format
=> 'pve-configid-list',
680 description
=> "The RRD consolidation function",
682 enum
=> [ 'AVERAGE', 'MAX' ],
690 filename
=> { type
=> 'string' },
696 return PVE
::Cluster
::create_rrd_graph
(
697 "pve2-vm/$param->{vmid}", $param->{timeframe
},
698 $param->{ds
}, $param->{cf
});
702 __PACKAGE__-
>register_method({
704 path
=> '{vmid}/rrddata',
706 protected
=> 1, # fixme: can we avoid that?
708 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
710 description
=> "Read VM RRD statistics",
712 additionalProperties
=> 0,
714 node
=> get_standard_option
('pve-node'),
715 vmid
=> get_standard_option
('pve-vmid'),
717 description
=> "Specify the time frame you are interested in.",
719 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
722 description
=> "The RRD consolidation function",
724 enum
=> [ 'AVERAGE', 'MAX' ],
739 return PVE
::Cluster
::create_rrd_data
(
740 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
744 __PACKAGE__-
>register_method({
746 path
=> '{vmid}/config',
749 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
751 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
754 additionalProperties
=> 0,
756 node
=> get_standard_option
('pve-node'),
757 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
759 description
=> "Get current values (instead of pending values).",
771 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
778 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
780 delete $conf->{snapshots
};
782 if (!$param->{current
}) {
783 foreach my $opt (keys %{$conf->{pending
}}) {
784 next if $opt eq 'delete';
785 my $value = $conf->{pending
}->{$opt};
786 next if ref($value); # just to be sure
787 $conf->{$opt} = $value;
789 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
790 foreach my $opt (keys %$pending_delete_hash) {
791 delete $conf->{$opt} if $conf->{$opt};
795 delete $conf->{pending
};
800 __PACKAGE__-
>register_method({
801 name
=> 'vm_pending',
802 path
=> '{vmid}/pending',
805 description
=> "Get virtual machine configuration, including pending changes.",
807 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
810 additionalProperties
=> 0,
812 node
=> get_standard_option
('pve-node'),
813 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
822 description
=> "Configuration option name.",
826 description
=> "Current value.",
831 description
=> "Pending value.",
836 description
=> "Indicates a pending delete request if present and not 0. " .
837 "The value 2 indicates a force-delete request.",
849 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
851 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
855 foreach my $opt (keys %$conf) {
856 next if ref($conf->{$opt});
857 my $item = { key
=> $opt };
858 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
859 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
860 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
864 foreach my $opt (keys %{$conf->{pending
}}) {
865 next if $opt eq 'delete';
866 next if ref($conf->{pending
}->{$opt}); # just to be sure
867 next if defined($conf->{$opt});
868 my $item = { key
=> $opt };
869 $item->{pending
} = $conf->{pending
}->{$opt};
873 while (my ($opt, $force) = each %$pending_delete_hash) {
874 next if $conf->{pending
}->{$opt}; # just to be sure
875 next if $conf->{$opt};
876 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
883 # POST/PUT {vmid}/config implementation
885 # The original API used PUT (idempotent) an we assumed that all operations
886 # are fast. But it turned out that almost any configuration change can
887 # involve hot-plug actions, or disk alloc/free. Such actions can take long
888 # time to complete and have side effects (not idempotent).
890 # The new implementation uses POST and forks a worker process. We added
891 # a new option 'background_delay'. If specified we wait up to
892 # 'background_delay' second for the worker task to complete. It returns null
893 # if the task is finished within that time, else we return the UPID.
895 my $update_vm_api = sub {
896 my ($param, $sync) = @_;
898 my $rpcenv = PVE
::RPCEnvironment
::get
();
900 my $authuser = $rpcenv->get_user();
902 my $node = extract_param
($param, 'node');
904 my $vmid = extract_param
($param, 'vmid');
906 my $digest = extract_param
($param, 'digest');
908 my $background_delay = extract_param
($param, 'background_delay');
910 my @paramarr = (); # used for log message
911 foreach my $key (sort keys %$param) {
912 push @paramarr, "-$key", $param->{$key};
915 my $skiplock = extract_param
($param, 'skiplock');
916 raise_param_exc
({ skiplock
=> "Only root may use this option." })
917 if $skiplock && $authuser ne 'root@pam';
919 my $delete_str = extract_param
($param, 'delete');
921 my $revert_str = extract_param
($param, 'revert');
923 my $force = extract_param
($param, 'force');
925 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
927 my $storecfg = PVE
::Storage
::config
();
929 my $defaults = PVE
::QemuServer
::load_defaults
();
931 &$resolve_cdrom_alias($param);
933 # now try to verify all parameters
936 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
937 if (!PVE
::QemuServer
::option_exists
($opt)) {
938 raise_param_exc
({ revert
=> "unknown option '$opt'" });
941 raise_param_exc
({ delete => "you can't use '-$opt' and " .
942 "-revert $opt' at the same time" })
943 if defined($param->{$opt});
949 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
950 $opt = 'ide2' if $opt eq 'cdrom';
952 raise_param_exc
({ delete => "you can't use '-$opt' and " .
953 "-delete $opt' at the same time" })
954 if defined($param->{$opt});
956 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
957 "-revert $opt' at the same time" })
960 if (!PVE
::QemuServer
::option_exists
($opt)) {
961 raise_param_exc
({ delete => "unknown option '$opt'" });
967 my $repl_conf = PVE
::ReplicationConfig-
>new();
968 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
969 my $check_replication = sub {
971 return if !$is_replicated;
972 my $volid = $drive->{file
};
973 return if !$volid || !($drive->{replicate
}//1);
974 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
975 my ($storeid, $format);
976 if ($volid =~ $NEW_DISK_RE) {
978 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
980 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
981 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
983 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
984 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
985 return if $scfg->{shared
};
986 die "cannot add non-replicatable volume to a replicated VM\n";
989 foreach my $opt (keys %$param) {
990 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
992 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
993 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
994 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
995 $check_replication->($drive);
996 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
997 } elsif ($opt =~ m/^net(\d+)$/) {
999 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1000 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1004 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1006 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1008 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1010 my $updatefn = sub {
1012 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1014 die "checksum missmatch (file change by other user?)\n"
1015 if $digest && $digest ne $conf->{digest
};
1017 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1019 foreach my $opt (keys %$revert) {
1020 if (defined($conf->{$opt})) {
1021 $param->{$opt} = $conf->{$opt};
1022 } elsif (defined($conf->{pending
}->{$opt})) {
1027 if ($param->{memory
} || defined($param->{balloon
})) {
1028 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1029 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1031 die "balloon value too large (must be smaller than assigned memory)\n"
1032 if $balloon && $balloon > $maxmem;
1035 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1039 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1041 # write updates to pending section
1043 my $modified = {}; # record what $option we modify
1045 foreach my $opt (@delete) {
1046 $modified->{$opt} = 1;
1047 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1048 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1049 warn "cannot delete '$opt' - not set in current configuration!\n";
1050 $modified->{$opt} = 0;
1054 if ($opt =~ m/^unused/) {
1055 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1056 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1057 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1058 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1059 delete $conf->{$opt};
1060 PVE
::QemuConfig-
>write_config($vmid, $conf);
1062 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1063 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1064 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1065 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1066 if defined($conf->{pending
}->{$opt});
1067 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1068 PVE
::QemuConfig-
>write_config($vmid, $conf);
1070 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1071 PVE
::QemuConfig-
>write_config($vmid, $conf);
1075 foreach my $opt (keys %$param) { # add/change
1076 $modified->{$opt} = 1;
1077 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1078 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1080 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1081 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1082 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1083 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1085 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1087 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1088 if defined($conf->{pending
}->{$opt});
1090 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1092 $conf->{pending
}->{$opt} = $param->{$opt};
1094 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1095 PVE
::QemuConfig-
>write_config($vmid, $conf);
1098 # remove pending changes when nothing changed
1099 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1100 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1101 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1103 return if !scalar(keys %{$conf->{pending
}});
1105 my $running = PVE
::QemuServer
::check_running
($vmid);
1107 # apply pending changes
1109 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1113 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1114 raise_param_exc
($errors) if scalar(keys %$errors);
1116 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1126 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1128 if ($background_delay) {
1130 # Note: It would be better to do that in the Event based HTTPServer
1131 # to avoid blocking call to sleep.
1133 my $end_time = time() + $background_delay;
1135 my $task = PVE
::Tools
::upid_decode
($upid);
1138 while (time() < $end_time) {
1139 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1141 sleep(1); # this gets interrupted when child process ends
1145 my $status = PVE
::Tools
::upid_read_status
($upid);
1146 return undef if $status eq 'OK';
1155 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1158 my $vm_config_perm_list = [
1163 'VM.Config.Network',
1165 'VM.Config.Options',
1168 __PACKAGE__-
>register_method({
1169 name
=> 'update_vm_async',
1170 path
=> '{vmid}/config',
1174 description
=> "Set virtual machine options (asynchrounous API).",
1176 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1179 additionalProperties
=> 0,
1180 properties
=> PVE
::QemuServer
::json_config_properties
(
1182 node
=> get_standard_option
('pve-node'),
1183 vmid
=> get_standard_option
('pve-vmid'),
1184 skiplock
=> get_standard_option
('skiplock'),
1186 type
=> 'string', format
=> 'pve-configid-list',
1187 description
=> "A list of settings you want to delete.",
1191 type
=> 'string', format
=> 'pve-configid-list',
1192 description
=> "Revert a pending change.",
1197 description
=> $opt_force_description,
1199 requires
=> 'delete',
1203 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1207 background_delay
=> {
1209 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1220 code
=> $update_vm_api,
1223 __PACKAGE__-
>register_method({
1224 name
=> 'update_vm',
1225 path
=> '{vmid}/config',
1229 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1231 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1234 additionalProperties
=> 0,
1235 properties
=> PVE
::QemuServer
::json_config_properties
(
1237 node
=> get_standard_option
('pve-node'),
1238 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1239 skiplock
=> get_standard_option
('skiplock'),
1241 type
=> 'string', format
=> 'pve-configid-list',
1242 description
=> "A list of settings you want to delete.",
1246 type
=> 'string', format
=> 'pve-configid-list',
1247 description
=> "Revert a pending change.",
1252 description
=> $opt_force_description,
1254 requires
=> 'delete',
1258 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1264 returns
=> { type
=> 'null' },
1267 &$update_vm_api($param, 1);
1273 __PACKAGE__-
>register_method({
1274 name
=> 'destroy_vm',
1279 description
=> "Destroy the vm (also delete all used/owned volumes).",
1281 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1284 additionalProperties
=> 0,
1286 node
=> get_standard_option
('pve-node'),
1287 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1288 skiplock
=> get_standard_option
('skiplock'),
1297 my $rpcenv = PVE
::RPCEnvironment
::get
();
1299 my $authuser = $rpcenv->get_user();
1301 my $vmid = $param->{vmid
};
1303 my $skiplock = $param->{skiplock
};
1304 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1305 if $skiplock && $authuser ne 'root@pam';
1308 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1310 my $storecfg = PVE
::Storage
::config
();
1312 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1314 die "unable to remove VM $vmid - used in HA resources\n"
1315 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1317 # do not allow destroy if there are replication jobs
1318 my $repl_conf = PVE
::ReplicationConfig-
>new();
1319 $repl_conf->check_for_existing_jobs($vmid);
1321 # early tests (repeat after locking)
1322 die "VM $vmid is running - destroy failed\n"
1323 if PVE
::QemuServer
::check_running
($vmid);
1328 syslog
('info', "destroy VM $vmid: $upid\n");
1330 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1332 PVE
::AccessControl
::remove_vm_access
($vmid);
1334 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1337 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1340 __PACKAGE__-
>register_method({
1342 path
=> '{vmid}/unlink',
1346 description
=> "Unlink/delete disk images.",
1348 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1351 additionalProperties
=> 0,
1353 node
=> get_standard_option
('pve-node'),
1354 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1356 type
=> 'string', format
=> 'pve-configid-list',
1357 description
=> "A list of disk IDs you want to delete.",
1361 description
=> $opt_force_description,
1366 returns
=> { type
=> 'null'},
1370 $param->{delete} = extract_param
($param, 'idlist');
1372 __PACKAGE__-
>update_vm($param);
1379 __PACKAGE__-
>register_method({
1381 path
=> '{vmid}/vncproxy',
1385 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1387 description
=> "Creates a TCP VNC proxy connections.",
1389 additionalProperties
=> 0,
1391 node
=> get_standard_option
('pve-node'),
1392 vmid
=> get_standard_option
('pve-vmid'),
1396 description
=> "starts websockify instead of vncproxy",
1401 additionalProperties
=> 0,
1403 user
=> { type
=> 'string' },
1404 ticket
=> { type
=> 'string' },
1405 cert
=> { type
=> 'string' },
1406 port
=> { type
=> 'integer' },
1407 upid
=> { type
=> 'string' },
1413 my $rpcenv = PVE
::RPCEnvironment
::get
();
1415 my $authuser = $rpcenv->get_user();
1417 my $vmid = $param->{vmid
};
1418 my $node = $param->{node
};
1419 my $websocket = $param->{websocket
};
1421 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1423 my $authpath = "/vms/$vmid";
1425 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1427 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1430 my ($remip, $family);
1433 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1434 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1435 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1436 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1438 $family = PVE
::Tools
::get_host_address_family
($node);
1441 my $port = PVE
::Tools
::next_vnc_port
($family);
1448 syslog
('info', "starting vnc proxy $upid\n");
1452 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1454 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1456 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1457 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1458 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1459 '-timeout', $timeout, '-authpath', $authpath,
1460 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1461 PVE
::Tools
::run_command
($cmd);
1464 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1466 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1468 my $sock = IO
::Socket
::IP-
>new(
1473 GetAddrInfoFlags
=> 0,
1474 ) or die "failed to create socket: $!\n";
1475 # Inside the worker we shouldn't have any previous alarms
1476 # running anyway...:
1478 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1480 accept(my $cli, $sock) or die "connection failed: $!\n";
1483 if (PVE
::Tools
::run_command
($cmd,
1484 output
=> '>&'.fileno($cli),
1485 input
=> '<&'.fileno($cli),
1488 die "Failed to run vncproxy.\n";
1495 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1497 PVE
::Tools
::wait_for_vnc_port
($port);
1508 __PACKAGE__-
>register_method({
1509 name
=> 'vncwebsocket',
1510 path
=> '{vmid}/vncwebsocket',
1513 description
=> "You also need to pass a valid ticket (vncticket).",
1514 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1516 description
=> "Opens a weksocket for VNC traffic.",
1518 additionalProperties
=> 0,
1520 node
=> get_standard_option
('pve-node'),
1521 vmid
=> get_standard_option
('pve-vmid'),
1523 description
=> "Ticket from previous call to vncproxy.",
1528 description
=> "Port number returned by previous vncproxy call.",
1538 port
=> { type
=> 'string' },
1544 my $rpcenv = PVE
::RPCEnvironment
::get
();
1546 my $authuser = $rpcenv->get_user();
1548 my $vmid = $param->{vmid
};
1549 my $node = $param->{node
};
1551 my $authpath = "/vms/$vmid";
1553 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1555 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1557 # Note: VNC ports are acessible from outside, so we do not gain any
1558 # security if we verify that $param->{port} belongs to VM $vmid. This
1559 # check is done by verifying the VNC ticket (inside VNC protocol).
1561 my $port = $param->{port
};
1563 return { port
=> $port };
1566 __PACKAGE__-
>register_method({
1567 name
=> 'spiceproxy',
1568 path
=> '{vmid}/spiceproxy',
1573 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1575 description
=> "Returns a SPICE configuration to connect to the VM.",
1577 additionalProperties
=> 0,
1579 node
=> get_standard_option
('pve-node'),
1580 vmid
=> get_standard_option
('pve-vmid'),
1581 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1584 returns
=> get_standard_option
('remote-viewer-config'),
1588 my $rpcenv = PVE
::RPCEnvironment
::get
();
1590 my $authuser = $rpcenv->get_user();
1592 my $vmid = $param->{vmid
};
1593 my $node = $param->{node
};
1594 my $proxy = $param->{proxy
};
1596 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1597 my $title = "VM $vmid";
1598 $title .= " - ". $conf->{name
} if $conf->{name
};
1600 my $port = PVE
::QemuServer
::spice_port
($vmid);
1602 my ($ticket, undef, $remote_viewer_config) =
1603 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1605 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1606 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1608 return $remote_viewer_config;
1611 __PACKAGE__-
>register_method({
1613 path
=> '{vmid}/status',
1616 description
=> "Directory index",
1621 additionalProperties
=> 0,
1623 node
=> get_standard_option
('pve-node'),
1624 vmid
=> get_standard_option
('pve-vmid'),
1632 subdir
=> { type
=> 'string' },
1635 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1641 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1644 { subdir
=> 'current' },
1645 { subdir
=> 'start' },
1646 { subdir
=> 'stop' },
1652 __PACKAGE__-
>register_method({
1653 name
=> 'vm_status',
1654 path
=> '{vmid}/status/current',
1657 protected
=> 1, # qemu pid files are only readable by root
1658 description
=> "Get virtual machine status.",
1660 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1663 additionalProperties
=> 0,
1665 node
=> get_standard_option
('pve-node'),
1666 vmid
=> get_standard_option
('pve-vmid'),
1669 returns
=> { type
=> 'object' },
1674 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1676 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1677 my $status = $vmstatus->{$param->{vmid
}};
1679 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1681 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1686 __PACKAGE__-
>register_method({
1688 path
=> '{vmid}/status/start',
1692 description
=> "Start virtual machine.",
1694 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1697 additionalProperties
=> 0,
1699 node
=> get_standard_option
('pve-node'),
1700 vmid
=> get_standard_option
('pve-vmid',
1701 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1702 skiplock
=> get_standard_option
('skiplock'),
1703 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1704 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1707 enum
=> ['secure', 'insecure'],
1708 description
=> "Migration traffic is encrypted using an SSH " .
1709 "tunnel by default. On secure, completely private networks " .
1710 "this can be disabled to increase performance.",
1713 migration_network
=> {
1714 type
=> 'string', format
=> 'CIDR',
1715 description
=> "CIDR of the (sub) network that is used for migration.",
1718 machine
=> get_standard_option
('pve-qm-machine'),
1720 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1732 my $rpcenv = PVE
::RPCEnvironment
::get
();
1734 my $authuser = $rpcenv->get_user();
1736 my $node = extract_param
($param, 'node');
1738 my $vmid = extract_param
($param, 'vmid');
1740 my $machine = extract_param
($param, 'machine');
1742 my $stateuri = extract_param
($param, 'stateuri');
1743 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1744 if $stateuri && $authuser ne 'root@pam';
1746 my $skiplock = extract_param
($param, 'skiplock');
1747 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1748 if $skiplock && $authuser ne 'root@pam';
1750 my $migratedfrom = extract_param
($param, 'migratedfrom');
1751 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1752 if $migratedfrom && $authuser ne 'root@pam';
1754 my $migration_type = extract_param
($param, 'migration_type');
1755 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1756 if $migration_type && $authuser ne 'root@pam';
1758 my $migration_network = extract_param
($param, 'migration_network');
1759 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1760 if $migration_network && $authuser ne 'root@pam';
1762 my $targetstorage = extract_param
($param, 'targetstorage');
1763 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1764 if $targetstorage && $authuser ne 'root@pam';
1766 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1767 if $targetstorage && !$migratedfrom;
1769 # read spice ticket from STDIN
1771 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1772 if (defined(my $line = <>)) {
1774 $spice_ticket = $line;
1778 PVE
::Cluster
::check_cfs_quorum
();
1780 my $storecfg = PVE
::Storage
::config
();
1782 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1783 $rpcenv->{type
} ne 'ha') {
1788 my $service = "vm:$vmid";
1790 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1792 print "Requesting HA start for VM $vmid\n";
1794 PVE
::Tools
::run_command
($cmd);
1799 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1806 syslog
('info', "start VM $vmid: $upid\n");
1808 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1809 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1814 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1818 __PACKAGE__-
>register_method({
1820 path
=> '{vmid}/status/stop',
1824 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1825 "is akin to pulling the power plug of a running computer and may damage the VM data",
1827 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1830 additionalProperties
=> 0,
1832 node
=> get_standard_option
('pve-node'),
1833 vmid
=> get_standard_option
('pve-vmid',
1834 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1835 skiplock
=> get_standard_option
('skiplock'),
1836 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1838 description
=> "Wait maximal timeout seconds.",
1844 description
=> "Do not deactivate storage volumes.",
1857 my $rpcenv = PVE
::RPCEnvironment
::get
();
1859 my $authuser = $rpcenv->get_user();
1861 my $node = extract_param
($param, 'node');
1863 my $vmid = extract_param
($param, 'vmid');
1865 my $skiplock = extract_param
($param, 'skiplock');
1866 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1867 if $skiplock && $authuser ne 'root@pam';
1869 my $keepActive = extract_param
($param, 'keepActive');
1870 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1871 if $keepActive && $authuser ne 'root@pam';
1873 my $migratedfrom = extract_param
($param, 'migratedfrom');
1874 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1875 if $migratedfrom && $authuser ne 'root@pam';
1878 my $storecfg = PVE
::Storage
::config
();
1880 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1885 my $service = "vm:$vmid";
1887 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1889 print "Requesting HA stop for VM $vmid\n";
1891 PVE
::Tools
::run_command
($cmd);
1896 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1902 syslog
('info', "stop VM $vmid: $upid\n");
1904 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1905 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1910 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1914 __PACKAGE__-
>register_method({
1916 path
=> '{vmid}/status/reset',
1920 description
=> "Reset virtual machine.",
1922 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1925 additionalProperties
=> 0,
1927 node
=> get_standard_option
('pve-node'),
1928 vmid
=> get_standard_option
('pve-vmid',
1929 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1930 skiplock
=> get_standard_option
('skiplock'),
1939 my $rpcenv = PVE
::RPCEnvironment
::get
();
1941 my $authuser = $rpcenv->get_user();
1943 my $node = extract_param
($param, 'node');
1945 my $vmid = extract_param
($param, 'vmid');
1947 my $skiplock = extract_param
($param, 'skiplock');
1948 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1949 if $skiplock && $authuser ne 'root@pam';
1951 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1956 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1961 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1964 __PACKAGE__-
>register_method({
1965 name
=> 'vm_shutdown',
1966 path
=> '{vmid}/status/shutdown',
1970 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1971 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1973 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1976 additionalProperties
=> 0,
1978 node
=> get_standard_option
('pve-node'),
1979 vmid
=> get_standard_option
('pve-vmid',
1980 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1981 skiplock
=> get_standard_option
('skiplock'),
1983 description
=> "Wait maximal timeout seconds.",
1989 description
=> "Make sure the VM stops.",
1995 description
=> "Do not deactivate storage volumes.",
2008 my $rpcenv = PVE
::RPCEnvironment
::get
();
2010 my $authuser = $rpcenv->get_user();
2012 my $node = extract_param
($param, 'node');
2014 my $vmid = extract_param
($param, 'vmid');
2016 my $skiplock = extract_param
($param, 'skiplock');
2017 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2018 if $skiplock && $authuser ne 'root@pam';
2020 my $keepActive = extract_param
($param, 'keepActive');
2021 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2022 if $keepActive && $authuser ne 'root@pam';
2024 my $storecfg = PVE
::Storage
::config
();
2028 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2029 # otherwise, we will infer a shutdown command, but run into the timeout,
2030 # then when the vm is resumed, it will instantly shutdown
2032 # checking the qmp status here to get feedback to the gui/cli/api
2033 # and the status query should not take too long
2036 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2040 if (!$err && $qmpstatus->{status
} eq "paused") {
2041 if ($param->{forceStop
}) {
2042 warn "VM is paused - stop instead of shutdown\n";
2045 die "VM is paused - cannot shutdown\n";
2049 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2050 ($rpcenv->{type
} ne 'ha')) {
2055 my $service = "vm:$vmid";
2057 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2059 print "Requesting HA stop for VM $vmid\n";
2061 PVE
::Tools
::run_command
($cmd);
2066 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2073 syslog
('info', "shutdown VM $vmid: $upid\n");
2075 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2076 $shutdown, $param->{forceStop
}, $keepActive);
2081 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2085 __PACKAGE__-
>register_method({
2086 name
=> 'vm_suspend',
2087 path
=> '{vmid}/status/suspend',
2091 description
=> "Suspend virtual machine.",
2093 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2096 additionalProperties
=> 0,
2098 node
=> get_standard_option
('pve-node'),
2099 vmid
=> get_standard_option
('pve-vmid',
2100 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2101 skiplock
=> get_standard_option
('skiplock'),
2110 my $rpcenv = PVE
::RPCEnvironment
::get
();
2112 my $authuser = $rpcenv->get_user();
2114 my $node = extract_param
($param, 'node');
2116 my $vmid = extract_param
($param, 'vmid');
2118 my $skiplock = extract_param
($param, 'skiplock');
2119 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2120 if $skiplock && $authuser ne 'root@pam';
2122 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2127 syslog
('info', "suspend VM $vmid: $upid\n");
2129 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2134 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2137 __PACKAGE__-
>register_method({
2138 name
=> 'vm_resume',
2139 path
=> '{vmid}/status/resume',
2143 description
=> "Resume virtual machine.",
2145 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2148 additionalProperties
=> 0,
2150 node
=> get_standard_option
('pve-node'),
2151 vmid
=> get_standard_option
('pve-vmid',
2152 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2153 skiplock
=> get_standard_option
('skiplock'),
2154 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2164 my $rpcenv = PVE
::RPCEnvironment
::get
();
2166 my $authuser = $rpcenv->get_user();
2168 my $node = extract_param
($param, 'node');
2170 my $vmid = extract_param
($param, 'vmid');
2172 my $skiplock = extract_param
($param, 'skiplock');
2173 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2174 if $skiplock && $authuser ne 'root@pam';
2176 my $nocheck = extract_param
($param, 'nocheck');
2178 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2183 syslog
('info', "resume VM $vmid: $upid\n");
2185 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2190 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2193 __PACKAGE__-
>register_method({
2194 name
=> 'vm_sendkey',
2195 path
=> '{vmid}/sendkey',
2199 description
=> "Send key event to virtual machine.",
2201 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2204 additionalProperties
=> 0,
2206 node
=> get_standard_option
('pve-node'),
2207 vmid
=> get_standard_option
('pve-vmid',
2208 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2209 skiplock
=> get_standard_option
('skiplock'),
2211 description
=> "The key (qemu monitor encoding).",
2216 returns
=> { type
=> 'null'},
2220 my $rpcenv = PVE
::RPCEnvironment
::get
();
2222 my $authuser = $rpcenv->get_user();
2224 my $node = extract_param
($param, 'node');
2226 my $vmid = extract_param
($param, 'vmid');
2228 my $skiplock = extract_param
($param, 'skiplock');
2229 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2230 if $skiplock && $authuser ne 'root@pam';
2232 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2237 __PACKAGE__-
>register_method({
2238 name
=> 'vm_feature',
2239 path
=> '{vmid}/feature',
2243 description
=> "Check if feature for virtual machine is available.",
2245 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2248 additionalProperties
=> 0,
2250 node
=> get_standard_option
('pve-node'),
2251 vmid
=> get_standard_option
('pve-vmid'),
2253 description
=> "Feature to check.",
2255 enum
=> [ 'snapshot', 'clone', 'copy' ],
2257 snapname
=> get_standard_option
('pve-snapshot-name', {
2265 hasFeature
=> { type
=> 'boolean' },
2268 items
=> { type
=> 'string' },
2275 my $node = extract_param
($param, 'node');
2277 my $vmid = extract_param
($param, 'vmid');
2279 my $snapname = extract_param
($param, 'snapname');
2281 my $feature = extract_param
($param, 'feature');
2283 my $running = PVE
::QemuServer
::check_running
($vmid);
2285 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2288 my $snap = $conf->{snapshots
}->{$snapname};
2289 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2292 my $storecfg = PVE
::Storage
::config
();
2294 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2295 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2298 hasFeature
=> $hasFeature,
2299 nodes
=> [ keys %$nodelist ],
2303 __PACKAGE__-
>register_method({
2305 path
=> '{vmid}/clone',
2309 description
=> "Create a copy of virtual machine/template.",
2311 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2312 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2313 "'Datastore.AllocateSpace' on any used storage.",
2316 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2318 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2319 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2324 additionalProperties
=> 0,
2326 node
=> get_standard_option
('pve-node'),
2327 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2328 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2331 type
=> 'string', format
=> 'dns-name',
2332 description
=> "Set a name for the new VM.",
2337 description
=> "Description for the new VM.",
2341 type
=> 'string', format
=> 'pve-poolid',
2342 description
=> "Add the new VM to the specified pool.",
2344 snapname
=> get_standard_option
('pve-snapshot-name', {
2347 storage
=> get_standard_option
('pve-storage-id', {
2348 description
=> "Target storage for full clone.",
2353 description
=> "Target format for file storage.",
2357 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2362 description
=> "Create a full copy of all disk. This is always done when " .
2363 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2366 target
=> get_standard_option
('pve-node', {
2367 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2378 my $rpcenv = PVE
::RPCEnvironment
::get
();
2380 my $authuser = $rpcenv->get_user();
2382 my $node = extract_param
($param, 'node');
2384 my $vmid = extract_param
($param, 'vmid');
2386 my $newid = extract_param
($param, 'newid');
2388 my $pool = extract_param
($param, 'pool');
2390 if (defined($pool)) {
2391 $rpcenv->check_pool_exist($pool);
2394 my $snapname = extract_param
($param, 'snapname');
2396 my $storage = extract_param
($param, 'storage');
2398 my $format = extract_param
($param, 'format');
2400 my $target = extract_param
($param, 'target');
2402 my $localnode = PVE
::INotify
::nodename
();
2404 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2406 PVE
::Cluster
::check_node_exists
($target) if $target;
2408 my $storecfg = PVE
::Storage
::config
();
2411 # check if storage is enabled on local node
2412 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2414 # check if storage is available on target node
2415 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2416 # clone only works if target storage is shared
2417 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2418 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2422 PVE
::Cluster
::check_cfs_quorum
();
2424 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2426 # exclusive lock if VM is running - else shared lock is enough;
2427 my $shared_lock = $running ?
0 : 1;
2431 # do all tests after lock
2432 # we also try to do all tests before we fork the worker
2434 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2436 PVE
::QemuConfig-
>check_lock($conf);
2438 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2440 die "unexpected state change\n" if $verify_running != $running;
2442 die "snapshot '$snapname' does not exist\n"
2443 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2445 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2447 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2449 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2451 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2453 die "unable to create VM $newid: config file already exists\n"
2456 my $newconf = { lock => 'clone' };
2461 foreach my $opt (keys %$oldconf) {
2462 my $value = $oldconf->{$opt};
2464 # do not copy snapshot related info
2465 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2466 $opt eq 'vmstate' || $opt eq 'snapstate';
2468 # no need to copy unused images, because VMID(owner) changes anyways
2469 next if $opt =~ m/^unused\d+$/;
2471 # always change MAC! address
2472 if ($opt =~ m/^net(\d+)$/) {
2473 my $net = PVE
::QemuServer
::parse_net
($value);
2474 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2475 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2476 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2477 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2478 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2479 die "unable to parse drive options for '$opt'\n" if !$drive;
2480 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2481 $newconf->{$opt} = $value; # simply copy configuration
2483 if ($param->{full
}) {
2484 die "Full clone feature is not supported for drive '$opt'\n"
2485 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2486 $fullclone->{$opt} = 1;
2488 # not full means clone instead of copy
2489 die "Linked clone feature is not supported for drive '$opt'\n"
2490 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2492 $drives->{$opt} = $drive;
2493 push @$vollist, $drive->{file
};
2496 # copy everything else
2497 $newconf->{$opt} = $value;
2501 # auto generate a new uuid
2502 my ($uuid, $uuid_str);
2503 UUID
::generate
($uuid);
2504 UUID
::unparse
($uuid, $uuid_str);
2505 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2506 $smbios1->{uuid
} = $uuid_str;
2507 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2509 delete $newconf->{template
};
2511 if ($param->{name
}) {
2512 $newconf->{name
} = $param->{name
};
2514 if ($oldconf->{name
}) {
2515 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2517 $newconf->{name
} = "Copy-of-VM-$vmid";
2521 if ($param->{description
}) {
2522 $newconf->{description
} = $param->{description
};
2525 # create empty/temp config - this fails if VM already exists on other node
2526 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2531 my $newvollist = [];
2535 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2537 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2539 my $total_jobs = scalar(keys %{$drives});
2542 foreach my $opt (keys %$drives) {
2543 my $drive = $drives->{$opt};
2544 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2546 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2547 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2548 $jobs, $skipcomplete, $oldconf->{agent
});
2550 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2552 PVE
::QemuConfig-
>write_config($newid, $newconf);
2556 delete $newconf->{lock};
2557 PVE
::QemuConfig-
>write_config($newid, $newconf);
2560 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2561 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2562 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2564 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2565 die "Failed to move config to node '$target' - rename failed: $!\n"
2566 if !rename($conffile, $newconffile);
2569 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2574 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2576 sleep 1; # some storage like rbd need to wait before release volume - really?
2578 foreach my $volid (@$newvollist) {
2579 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2582 die "clone failed: $err";
2588 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2590 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2593 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2594 # Aquire exclusive lock lock for $newid
2595 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2600 __PACKAGE__-
>register_method({
2601 name
=> 'move_vm_disk',
2602 path
=> '{vmid}/move_disk',
2606 description
=> "Move volume to different storage.",
2608 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2610 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2611 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2615 additionalProperties
=> 0,
2617 node
=> get_standard_option
('pve-node'),
2618 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2621 description
=> "The disk you want to move.",
2622 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2624 storage
=> get_standard_option
('pve-storage-id', {
2625 description
=> "Target storage.",
2626 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2630 description
=> "Target Format.",
2631 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2636 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2642 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2650 description
=> "the task ID.",
2655 my $rpcenv = PVE
::RPCEnvironment
::get
();
2657 my $authuser = $rpcenv->get_user();
2659 my $node = extract_param
($param, 'node');
2661 my $vmid = extract_param
($param, 'vmid');
2663 my $digest = extract_param
($param, 'digest');
2665 my $disk = extract_param
($param, 'disk');
2667 my $storeid = extract_param
($param, 'storage');
2669 my $format = extract_param
($param, 'format');
2671 my $storecfg = PVE
::Storage
::config
();
2673 my $updatefn = sub {
2675 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2677 PVE
::QemuConfig-
>check_lock($conf);
2679 die "checksum missmatch (file change by other user?)\n"
2680 if $digest && $digest ne $conf->{digest
};
2682 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2684 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2686 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2688 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2691 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2692 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2696 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2697 (!$format || !$oldfmt || $oldfmt eq $format);
2699 # this only checks snapshots because $disk is passed!
2700 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2701 die "you can't move a disk with snapshots and delete the source\n"
2702 if $snapshotted && $param->{delete};
2704 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2706 my $running = PVE
::QemuServer
::check_running
($vmid);
2708 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2712 my $newvollist = [];
2715 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2717 warn "moving disk with snapshots, snapshots will not be moved!\n"
2720 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2721 $vmid, $storeid, $format, 1, $newvollist);
2723 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2725 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2727 # convert moved disk to base if part of template
2728 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2729 if PVE
::QemuConfig-
>is_template($conf);
2731 PVE
::QemuConfig-
>write_config($vmid, $conf);
2734 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2735 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2742 foreach my $volid (@$newvollist) {
2743 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2746 die "storage migration failed: $err";
2749 if ($param->{delete}) {
2751 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2752 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2758 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2761 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2764 __PACKAGE__-
>register_method({
2765 name
=> 'migrate_vm',
2766 path
=> '{vmid}/migrate',
2770 description
=> "Migrate virtual machine. Creates a new migration task.",
2772 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2775 additionalProperties
=> 0,
2777 node
=> get_standard_option
('pve-node'),
2778 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2779 target
=> get_standard_option
('pve-node', {
2780 description
=> "Target node.",
2781 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2785 description
=> "Use online/live migration.",
2790 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2795 enum
=> ['secure', 'insecure'],
2796 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2799 migration_network
=> {
2800 type
=> 'string', format
=> 'CIDR',
2801 description
=> "CIDR of the (sub) network that is used for migration.",
2804 "with-local-disks" => {
2806 description
=> "Enable live storage migration for local disk",
2809 targetstorage
=> get_standard_option
('pve-storage-id', {
2810 description
=> "Default target storage.",
2812 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2818 description
=> "the task ID.",
2823 my $rpcenv = PVE
::RPCEnvironment
::get
();
2825 my $authuser = $rpcenv->get_user();
2827 my $target = extract_param
($param, 'target');
2829 my $localnode = PVE
::INotify
::nodename
();
2830 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2832 PVE
::Cluster
::check_cfs_quorum
();
2834 PVE
::Cluster
::check_node_exists
($target);
2836 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2838 my $vmid = extract_param
($param, 'vmid');
2840 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2841 if !$param->{online
} && $param->{targetstorage
};
2843 raise_param_exc
({ force
=> "Only root may use this option." })
2844 if $param->{force
} && $authuser ne 'root@pam';
2846 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2847 if $param->{migration_type
} && $authuser ne 'root@pam';
2849 # allow root only until better network permissions are available
2850 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2851 if $param->{migration_network
} && $authuser ne 'root@pam';
2854 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2856 # try to detect errors early
2858 PVE
::QemuConfig-
>check_lock($conf);
2860 if (PVE
::QemuServer
::check_running
($vmid)) {
2861 die "cant migrate running VM without --online\n"
2862 if !$param->{online
};
2865 my $storecfg = PVE
::Storage
::config
();
2867 if( $param->{targetstorage
}) {
2868 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2870 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2873 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2878 my $service = "vm:$vmid";
2880 my $cmd = ['ha-manager', 'migrate', $service, $target];
2882 print "Requesting HA migration for VM $vmid to node $target\n";
2884 PVE
::Tools
::run_command
($cmd);
2889 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2894 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2898 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2901 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2906 __PACKAGE__-
>register_method({
2908 path
=> '{vmid}/monitor',
2912 description
=> "Execute Qemu monitor commands.",
2914 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2915 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2918 additionalProperties
=> 0,
2920 node
=> get_standard_option
('pve-node'),
2921 vmid
=> get_standard_option
('pve-vmid'),
2924 description
=> "The monitor command.",
2928 returns
=> { type
=> 'string'},
2932 my $rpcenv = PVE
::RPCEnvironment
::get
();
2933 my $authuser = $rpcenv->get_user();
2936 my $command = shift;
2937 return $command =~ m/^\s*info(\s+|$)/
2938 || $command =~ m/^\s*help\s*$/;
2941 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2942 if !&$is_ro($param->{command
});
2944 my $vmid = $param->{vmid
};
2946 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2950 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2952 $res = "ERROR: $@" if $@;
2957 my $guest_agent_commands = [
2965 'network-get-interfaces',
2968 'get-memory-blocks',
2969 'get-memory-block-info',
2976 __PACKAGE__-
>register_method({
2978 path
=> '{vmid}/agent',
2982 description
=> "Execute Qemu Guest Agent commands.",
2984 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2987 additionalProperties
=> 0,
2989 node
=> get_standard_option
('pve-node'),
2990 vmid
=> get_standard_option
('pve-vmid', {
2991 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2994 description
=> "The QGA command.",
2995 enum
=> $guest_agent_commands,
3001 description
=> "Returns an object with a single `result` property. The type of that
3002 property depends on the executed command.",
3007 my $vmid = $param->{vmid
};
3009 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3011 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3012 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3014 my $cmd = $param->{command
};
3016 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3018 return { result
=> $res };
3021 __PACKAGE__-
>register_method({
3022 name
=> 'resize_vm',
3023 path
=> '{vmid}/resize',
3027 description
=> "Extend volume size.",
3029 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3032 additionalProperties
=> 0,
3034 node
=> get_standard_option
('pve-node'),
3035 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3036 skiplock
=> get_standard_option
('skiplock'),
3039 description
=> "The disk you want to resize.",
3040 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3044 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3045 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.",
3049 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3055 returns
=> { type
=> 'null'},
3059 my $rpcenv = PVE
::RPCEnvironment
::get
();
3061 my $authuser = $rpcenv->get_user();
3063 my $node = extract_param
($param, 'node');
3065 my $vmid = extract_param
($param, 'vmid');
3067 my $digest = extract_param
($param, 'digest');
3069 my $disk = extract_param
($param, 'disk');
3071 my $sizestr = extract_param
($param, 'size');
3073 my $skiplock = extract_param
($param, 'skiplock');
3074 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3075 if $skiplock && $authuser ne 'root@pam';
3077 my $storecfg = PVE
::Storage
::config
();
3079 my $updatefn = sub {
3081 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3083 die "checksum missmatch (file change by other user?)\n"
3084 if $digest && $digest ne $conf->{digest
};
3085 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3087 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3089 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3091 my (undef, undef, undef, undef, undef, undef, $format) =
3092 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3094 die "can't resize volume: $disk if snapshot exists\n"
3095 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3097 my $volid = $drive->{file
};
3099 die "disk '$disk' has no associated volume\n" if !$volid;
3101 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3103 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3105 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3107 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3108 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3110 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3111 my ($ext, $newsize, $unit) = ($1, $2, $4);
3114 $newsize = $newsize * 1024;
3115 } elsif ($unit eq 'M') {
3116 $newsize = $newsize * 1024 * 1024;
3117 } elsif ($unit eq 'G') {
3118 $newsize = $newsize * 1024 * 1024 * 1024;
3119 } elsif ($unit eq 'T') {
3120 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3123 $newsize += $size if $ext;
3124 $newsize = int($newsize);
3126 die "shrinking disks is not supported\n" if $newsize < $size;
3128 return if $size == $newsize;
3130 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3132 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3134 $drive->{size
} = $newsize;
3135 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3137 PVE
::QemuConfig-
>write_config($vmid, $conf);
3140 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3144 __PACKAGE__-
>register_method({
3145 name
=> 'snapshot_list',
3146 path
=> '{vmid}/snapshot',
3148 description
=> "List all snapshots.",
3150 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3153 protected
=> 1, # qemu pid files are only readable by root
3155 additionalProperties
=> 0,
3157 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3158 node
=> get_standard_option
('pve-node'),
3167 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3172 my $vmid = $param->{vmid
};
3174 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3175 my $snaphash = $conf->{snapshots
} || {};
3179 foreach my $name (keys %$snaphash) {
3180 my $d = $snaphash->{$name};
3183 snaptime
=> $d->{snaptime
} || 0,
3184 vmstate
=> $d->{vmstate
} ?
1 : 0,
3185 description
=> $d->{description
} || '',
3187 $item->{parent
} = $d->{parent
} if $d->{parent
};
3188 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3192 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3193 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3194 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3196 push @$res, $current;
3201 __PACKAGE__-
>register_method({
3203 path
=> '{vmid}/snapshot',
3207 description
=> "Snapshot a VM.",
3209 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3212 additionalProperties
=> 0,
3214 node
=> get_standard_option
('pve-node'),
3215 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3216 snapname
=> get_standard_option
('pve-snapshot-name'),
3220 description
=> "Save the vmstate",
3225 description
=> "A textual description or comment.",
3231 description
=> "the task ID.",
3236 my $rpcenv = PVE
::RPCEnvironment
::get
();
3238 my $authuser = $rpcenv->get_user();
3240 my $node = extract_param
($param, 'node');
3242 my $vmid = extract_param
($param, 'vmid');
3244 my $snapname = extract_param
($param, 'snapname');
3246 die "unable to use snapshot name 'current' (reserved name)\n"
3247 if $snapname eq 'current';
3250 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3251 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3252 $param->{description
});
3255 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3258 __PACKAGE__-
>register_method({
3259 name
=> 'snapshot_cmd_idx',
3260 path
=> '{vmid}/snapshot/{snapname}',
3267 additionalProperties
=> 0,
3269 vmid
=> get_standard_option
('pve-vmid'),
3270 node
=> get_standard_option
('pve-node'),
3271 snapname
=> get_standard_option
('pve-snapshot-name'),
3280 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3287 push @$res, { cmd
=> 'rollback' };
3288 push @$res, { cmd
=> 'config' };
3293 __PACKAGE__-
>register_method({
3294 name
=> 'update_snapshot_config',
3295 path
=> '{vmid}/snapshot/{snapname}/config',
3299 description
=> "Update snapshot metadata.",
3301 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3304 additionalProperties
=> 0,
3306 node
=> get_standard_option
('pve-node'),
3307 vmid
=> get_standard_option
('pve-vmid'),
3308 snapname
=> get_standard_option
('pve-snapshot-name'),
3312 description
=> "A textual description or comment.",
3316 returns
=> { type
=> 'null' },
3320 my $rpcenv = PVE
::RPCEnvironment
::get
();
3322 my $authuser = $rpcenv->get_user();
3324 my $vmid = extract_param
($param, 'vmid');
3326 my $snapname = extract_param
($param, 'snapname');
3328 return undef if !defined($param->{description
});
3330 my $updatefn = sub {
3332 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3334 PVE
::QemuConfig-
>check_lock($conf);
3336 my $snap = $conf->{snapshots
}->{$snapname};
3338 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3340 $snap->{description
} = $param->{description
} if defined($param->{description
});
3342 PVE
::QemuConfig-
>write_config($vmid, $conf);
3345 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3350 __PACKAGE__-
>register_method({
3351 name
=> 'get_snapshot_config',
3352 path
=> '{vmid}/snapshot/{snapname}/config',
3355 description
=> "Get snapshot configuration",
3357 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3360 additionalProperties
=> 0,
3362 node
=> get_standard_option
('pve-node'),
3363 vmid
=> get_standard_option
('pve-vmid'),
3364 snapname
=> get_standard_option
('pve-snapshot-name'),
3367 returns
=> { type
=> "object" },
3371 my $rpcenv = PVE
::RPCEnvironment
::get
();
3373 my $authuser = $rpcenv->get_user();
3375 my $vmid = extract_param
($param, 'vmid');
3377 my $snapname = extract_param
($param, 'snapname');
3379 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3381 my $snap = $conf->{snapshots
}->{$snapname};
3383 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3388 __PACKAGE__-
>register_method({
3390 path
=> '{vmid}/snapshot/{snapname}/rollback',
3394 description
=> "Rollback VM state to specified snapshot.",
3396 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3399 additionalProperties
=> 0,
3401 node
=> get_standard_option
('pve-node'),
3402 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3403 snapname
=> get_standard_option
('pve-snapshot-name'),
3408 description
=> "the task ID.",
3413 my $rpcenv = PVE
::RPCEnvironment
::get
();
3415 my $authuser = $rpcenv->get_user();
3417 my $node = extract_param
($param, 'node');
3419 my $vmid = extract_param
($param, 'vmid');
3421 my $snapname = extract_param
($param, 'snapname');
3424 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3425 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3429 # hold migration lock, this makes sure that nobody create replication snapshots
3430 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3433 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3436 __PACKAGE__-
>register_method({
3437 name
=> 'delsnapshot',
3438 path
=> '{vmid}/snapshot/{snapname}',
3442 description
=> "Delete a VM snapshot.",
3444 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3447 additionalProperties
=> 0,
3449 node
=> get_standard_option
('pve-node'),
3450 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3451 snapname
=> get_standard_option
('pve-snapshot-name'),
3455 description
=> "For removal from config file, even if removing disk snapshots fails.",
3461 description
=> "the task ID.",
3466 my $rpcenv = PVE
::RPCEnvironment
::get
();
3468 my $authuser = $rpcenv->get_user();
3470 my $node = extract_param
($param, 'node');
3472 my $vmid = extract_param
($param, 'vmid');
3474 my $snapname = extract_param
($param, 'snapname');
3477 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3478 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3481 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3484 __PACKAGE__-
>register_method({
3486 path
=> '{vmid}/template',
3490 description
=> "Create a Template.",
3492 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3493 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3496 additionalProperties
=> 0,
3498 node
=> get_standard_option
('pve-node'),
3499 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3503 description
=> "If you want to convert only 1 disk to base image.",
3504 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3509 returns
=> { type
=> 'null'},
3513 my $rpcenv = PVE
::RPCEnvironment
::get
();
3515 my $authuser = $rpcenv->get_user();
3517 my $node = extract_param
($param, 'node');
3519 my $vmid = extract_param
($param, 'vmid');
3521 my $disk = extract_param
($param, 'disk');
3523 my $updatefn = sub {
3525 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3527 PVE
::QemuConfig-
>check_lock($conf);
3529 die "unable to create template, because VM contains snapshots\n"
3530 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3532 die "you can't convert a template to a template\n"
3533 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3535 die "you can't convert a VM to template if VM is running\n"
3536 if PVE
::QemuServer
::check_running
($vmid);
3539 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3542 $conf->{template
} = 1;
3543 PVE
::QemuConfig-
>write_config($vmid, $conf);
3545 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3548 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);