1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::ReplicationConfig
;
19 use PVE
::GuestHelpers
;
23 use PVE
::RPCEnvironment
;
24 use PVE
::AccessControl
;
28 use PVE
::API2
::Firewall
::VM
;
31 if (!$ENV{PVE_GENERATING_DOCS
}) {
32 require PVE
::HA
::Env
::PVE2
;
33 import PVE
::HA
::Env
::PVE2
;
34 require PVE
::HA
::Config
;
35 import PVE
::HA
::Config
;
39 use Data
::Dumper
; # fixme: remove
41 use base
qw(PVE::RESTHandler);
43 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
45 my $resolve_cdrom_alias = sub {
48 if (my $value = $param->{cdrom
}) {
49 $value .= ",media=cdrom" if $value !~ m/media=/;
50 $param->{ide2
} = $value;
51 delete $param->{cdrom
};
55 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
56 my $check_storage_access = sub {
57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
59 PVE
::QemuServer
::foreach_drive
($settings, sub {
60 my ($ds, $drive) = @_;
62 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
64 my $volid = $drive->{file
};
66 if (!$volid || $volid eq 'none') {
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
70 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
74 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
75 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
76 if !$scfg->{content
}->{images
};
78 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
82 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
83 if defined($settings->{vmstatestorage
});
86 my $check_storage_access_clone = sub {
87 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
91 PVE
::QemuServer
::foreach_drive
($conf, sub {
92 my ($ds, $drive) = @_;
94 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
96 my $volid = $drive->{file
};
98 return if !$volid || $volid eq 'none';
101 if ($volid eq 'cdrom') {
102 $rpcenv->check($authuser, "/", ['Sys.Console']);
104 # we simply allow access
105 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
106 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
107 $sharedvm = 0 if !$scfg->{shared
};
111 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
113 $sharedvm = 0 if !$scfg->{shared
};
115 $sid = $storage if $storage;
116 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
120 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
121 if defined($conf->{vmstatestorage
});
126 # Note: $pool is only needed when creating a VM, because pool permissions
127 # are automatically inherited if VM already exists inside a pool.
128 my $create_disks = sub {
129 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
136 my ($ds, $disk) = @_;
138 my $volid = $disk->{file
};
140 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
141 delete $disk->{size
};
142 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
143 } elsif ($volid =~ $NEW_DISK_RE) {
144 my ($storeid, $size) = ($2 || $default_storage, $3);
145 die "no storage ID specified (and no default storage)\n" if !$storeid;
146 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
147 my $fmt = $disk->{format
} || $defformat;
149 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
152 if ($ds eq 'efidisk0') {
153 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt);
155 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
157 push @$vollist, $volid;
158 $disk->{file
} = $volid;
159 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
160 delete $disk->{format
}; # no longer needed
161 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
164 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
166 my $volid_is_new = 1;
169 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
170 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
175 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
177 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
179 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
181 die "volume $volid does not exists\n" if !$size;
183 $disk->{size
} = $size;
186 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
192 # free allocated images on error
194 syslog
('err', "VM $vmid creating disks failed");
195 foreach my $volid (@$vollist) {
196 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
202 # modify vm config if everything went well
203 foreach my $ds (keys %$res) {
204 $conf->{$ds} = $res->{$ds};
221 my $memoryoptions = {
227 my $hwtypeoptions = {
239 my $generaloptions = {
246 'migrate_downtime' => 1,
247 'migrate_speed' => 1,
259 my $vmpoweroptions = {
266 'vmstatestorage' => 1,
269 my $check_vm_modify_config_perm = sub {
270 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
272 return 1 if $authuser eq 'root@pam';
274 foreach my $opt (@$key_list) {
275 # disk checks need to be done somewhere else
276 next if PVE
::QemuServer
::is_valid_drivename
($opt);
277 next if $opt eq 'cdrom';
278 next if $opt =~ m/^unused\d+$/;
280 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
281 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
282 } elsif ($memoryoptions->{$opt}) {
283 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
284 } elsif ($hwtypeoptions->{$opt}) {
285 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
286 } elsif ($generaloptions->{$opt}) {
287 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
288 # special case for startup since it changes host behaviour
289 if ($opt eq 'startup') {
290 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
292 } elsif ($vmpoweroptions->{$opt}) {
293 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
294 } elsif ($diskoptions->{$opt}) {
295 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
296 } elsif ($opt =~ m/^net\d+$/) {
297 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
299 # catches usb\d+, hostpci\d+, args, lock, etc.
300 # new options will be checked here
301 die "only root can set '$opt' config\n";
308 __PACKAGE__-
>register_method({
312 description
=> "Virtual machine index (per node).",
314 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
318 protected
=> 1, # qemu pid files are only readable by root
320 additionalProperties
=> 0,
322 node
=> get_standard_option
('pve-node'),
326 description
=> "Determine the full status of active VMs.",
336 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
341 my $rpcenv = PVE
::RPCEnvironment
::get
();
342 my $authuser = $rpcenv->get_user();
344 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
347 foreach my $vmid (keys %$vmstatus) {
348 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
350 my $data = $vmstatus->{$vmid};
351 $data->{vmid
} = int($vmid);
360 __PACKAGE__-
>register_method({
364 description
=> "Create or restore a virtual machine.",
366 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
367 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
368 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
369 user
=> 'all', # check inside
374 additionalProperties
=> 0,
375 properties
=> PVE
::QemuServer
::json_config_properties
(
377 node
=> get_standard_option
('pve-node'),
378 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
380 description
=> "The backup file.",
384 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
386 storage
=> get_standard_option
('pve-storage-id', {
387 description
=> "Default storage.",
389 completion
=> \
&PVE
::QemuServer
::complete_storage
,
394 description
=> "Allow to overwrite existing VM.",
395 requires
=> 'archive',
400 description
=> "Assign a unique random ethernet address.",
401 requires
=> 'archive',
405 type
=> 'string', format
=> 'pve-poolid',
406 description
=> "Add the VM to the specified pool.",
416 my $rpcenv = PVE
::RPCEnvironment
::get
();
418 my $authuser = $rpcenv->get_user();
420 my $node = extract_param
($param, 'node');
422 my $vmid = extract_param
($param, 'vmid');
424 my $archive = extract_param
($param, 'archive');
426 my $storage = extract_param
($param, 'storage');
428 my $force = extract_param
($param, 'force');
430 my $unique = extract_param
($param, 'unique');
432 my $pool = extract_param
($param, 'pool');
434 my $filename = PVE
::QemuConfig-
>config_file($vmid);
436 my $storecfg = PVE
::Storage
::config
();
438 PVE
::Cluster
::check_cfs_quorum
();
440 if (defined($pool)) {
441 $rpcenv->check_pool_exist($pool);
444 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
445 if defined($storage);
447 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
449 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
451 } elsif ($archive && $force && (-f
$filename) &&
452 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
453 # OK: user has VM.Backup permissions, and want to restore an existing VM
459 &$resolve_cdrom_alias($param);
461 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
463 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
465 foreach my $opt (keys %$param) {
466 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
467 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
468 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
470 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
471 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
475 PVE
::QemuServer
::add_random_macs
($param);
477 my $keystr = join(' ', keys %$param);
478 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
480 if ($archive eq '-') {
481 die "pipe requires cli environment\n"
482 if $rpcenv->{type
} ne 'cli';
484 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
485 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
489 my $restorefn = sub {
490 my $vmlist = PVE
::Cluster
::get_vmlist
();
491 if ($vmlist->{ids
}->{$vmid}) {
492 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
493 if ($current_node eq $node) {
494 my $conf = PVE
::QemuConfig-
>load_config($vmid);
496 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
498 die "unable to restore vm $vmid - config file already exists\n"
501 die "unable to restore vm $vmid - vm is running\n"
502 if PVE
::QemuServer
::check_running
($vmid);
504 die "unable to restore vm $vmid - vm is a template\n"
505 if PVE
::QemuConfig-
>is_template($conf);
508 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
513 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
516 unique
=> $unique });
518 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
521 # ensure no old replication state are exists
522 PVE
::ReplicationState
::delete_guest_states
($vmid);
524 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
530 PVE
::Cluster
::check_vmid_unused
($vmid);
532 # ensure no old replication state are exists
533 PVE
::ReplicationState
::delete_guest_states
($vmid);
543 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
545 # try to be smart about bootdisk
546 my @disks = PVE
::QemuServer
::valid_drive_names
();
548 foreach my $ds (reverse @disks) {
549 next if !$conf->{$ds};
550 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
551 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
555 if (!$conf->{bootdisk
} && $firstdisk) {
556 $conf->{bootdisk
} = $firstdisk;
559 # auto generate uuid if user did not specify smbios1 option
560 if (!$conf->{smbios1
}) {
561 my ($uuid, $uuid_str);
562 UUID
::generate
($uuid);
563 UUID
::unparse
($uuid, $uuid_str);
564 $conf->{smbios1
} = "uuid=$uuid_str";
567 PVE
::QemuConfig-
>write_config($vmid, $conf);
573 foreach my $volid (@$vollist) {
574 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
577 die "create failed - $err";
580 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
583 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
586 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
589 __PACKAGE__-
>register_method({
594 description
=> "Directory index",
599 additionalProperties
=> 0,
601 node
=> get_standard_option
('pve-node'),
602 vmid
=> get_standard_option
('pve-vmid'),
610 subdir
=> { type
=> 'string' },
613 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
619 { subdir
=> 'config' },
620 { subdir
=> 'pending' },
621 { subdir
=> 'status' },
622 { subdir
=> 'unlink' },
623 { subdir
=> 'vncproxy' },
624 { subdir
=> 'migrate' },
625 { subdir
=> 'resize' },
626 { subdir
=> 'move' },
628 { subdir
=> 'rrddata' },
629 { subdir
=> 'monitor' },
630 { subdir
=> 'agent' },
631 { subdir
=> 'snapshot' },
632 { subdir
=> 'spiceproxy' },
633 { subdir
=> 'sendkey' },
634 { subdir
=> 'firewall' },
640 __PACKAGE__-
>register_method ({
641 subclass
=> "PVE::API2::Firewall::VM",
642 path
=> '{vmid}/firewall',
645 __PACKAGE__-
>register_method({
647 path
=> '{vmid}/rrd',
649 protected
=> 1, # fixme: can we avoid that?
651 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
653 description
=> "Read VM RRD statistics (returns PNG)",
655 additionalProperties
=> 0,
657 node
=> get_standard_option
('pve-node'),
658 vmid
=> get_standard_option
('pve-vmid'),
660 description
=> "Specify the time frame you are interested in.",
662 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
665 description
=> "The list of datasources you want to display.",
666 type
=> 'string', format
=> 'pve-configid-list',
669 description
=> "The RRD consolidation function",
671 enum
=> [ 'AVERAGE', 'MAX' ],
679 filename
=> { type
=> 'string' },
685 return PVE
::Cluster
::create_rrd_graph
(
686 "pve2-vm/$param->{vmid}", $param->{timeframe
},
687 $param->{ds
}, $param->{cf
});
691 __PACKAGE__-
>register_method({
693 path
=> '{vmid}/rrddata',
695 protected
=> 1, # fixme: can we avoid that?
697 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
699 description
=> "Read VM RRD statistics",
701 additionalProperties
=> 0,
703 node
=> get_standard_option
('pve-node'),
704 vmid
=> get_standard_option
('pve-vmid'),
706 description
=> "Specify the time frame you are interested in.",
708 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
711 description
=> "The RRD consolidation function",
713 enum
=> [ 'AVERAGE', 'MAX' ],
728 return PVE
::Cluster
::create_rrd_data
(
729 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
733 __PACKAGE__-
>register_method({
735 path
=> '{vmid}/config',
738 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
743 additionalProperties
=> 0,
745 node
=> get_standard_option
('pve-node'),
746 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
748 description
=> "Get current values (instead of pending values).",
760 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
767 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
769 delete $conf->{snapshots
};
771 if (!$param->{current
}) {
772 foreach my $opt (keys %{$conf->{pending
}}) {
773 next if $opt eq 'delete';
774 my $value = $conf->{pending
}->{$opt};
775 next if ref($value); # just to be sure
776 $conf->{$opt} = $value;
778 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
779 foreach my $opt (keys %$pending_delete_hash) {
780 delete $conf->{$opt} if $conf->{$opt};
784 delete $conf->{pending
};
789 __PACKAGE__-
>register_method({
790 name
=> 'vm_pending',
791 path
=> '{vmid}/pending',
794 description
=> "Get virtual machine configuration, including pending changes.",
796 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
799 additionalProperties
=> 0,
801 node
=> get_standard_option
('pve-node'),
802 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
811 description
=> "Configuration option name.",
815 description
=> "Current value.",
820 description
=> "Pending value.",
825 description
=> "Indicates a pending delete request if present and not 0. " .
826 "The value 2 indicates a force-delete request.",
838 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
840 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
844 foreach my $opt (keys %$conf) {
845 next if ref($conf->{$opt});
846 my $item = { key
=> $opt };
847 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
848 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
849 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
853 foreach my $opt (keys %{$conf->{pending
}}) {
854 next if $opt eq 'delete';
855 next if ref($conf->{pending
}->{$opt}); # just to be sure
856 next if defined($conf->{$opt});
857 my $item = { key
=> $opt };
858 $item->{pending
} = $conf->{pending
}->{$opt};
862 while (my ($opt, $force) = each %$pending_delete_hash) {
863 next if $conf->{pending
}->{$opt}; # just to be sure
864 next if $conf->{$opt};
865 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
872 # POST/PUT {vmid}/config implementation
874 # The original API used PUT (idempotent) an we assumed that all operations
875 # are fast. But it turned out that almost any configuration change can
876 # involve hot-plug actions, or disk alloc/free. Such actions can take long
877 # time to complete and have side effects (not idempotent).
879 # The new implementation uses POST and forks a worker process. We added
880 # a new option 'background_delay'. If specified we wait up to
881 # 'background_delay' second for the worker task to complete. It returns null
882 # if the task is finished within that time, else we return the UPID.
884 my $update_vm_api = sub {
885 my ($param, $sync) = @_;
887 my $rpcenv = PVE
::RPCEnvironment
::get
();
889 my $authuser = $rpcenv->get_user();
891 my $node = extract_param
($param, 'node');
893 my $vmid = extract_param
($param, 'vmid');
895 my $digest = extract_param
($param, 'digest');
897 my $background_delay = extract_param
($param, 'background_delay');
899 my @paramarr = (); # used for log message
900 foreach my $key (sort keys %$param) {
901 push @paramarr, "-$key", $param->{$key};
904 my $skiplock = extract_param
($param, 'skiplock');
905 raise_param_exc
({ skiplock
=> "Only root may use this option." })
906 if $skiplock && $authuser ne 'root@pam';
908 my $delete_str = extract_param
($param, 'delete');
910 my $revert_str = extract_param
($param, 'revert');
912 my $force = extract_param
($param, 'force');
914 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
916 my $storecfg = PVE
::Storage
::config
();
918 my $defaults = PVE
::QemuServer
::load_defaults
();
920 &$resolve_cdrom_alias($param);
922 # now try to verify all parameters
925 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
926 if (!PVE
::QemuServer
::option_exists
($opt)) {
927 raise_param_exc
({ revert
=> "unknown option '$opt'" });
930 raise_param_exc
({ delete => "you can't use '-$opt' and " .
931 "-revert $opt' at the same time" })
932 if defined($param->{$opt});
938 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
939 $opt = 'ide2' if $opt eq 'cdrom';
941 raise_param_exc
({ delete => "you can't use '-$opt' and " .
942 "-delete $opt' at the same time" })
943 if defined($param->{$opt});
945 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
946 "-revert $opt' at the same time" })
949 if (!PVE
::QemuServer
::option_exists
($opt)) {
950 raise_param_exc
({ delete => "unknown option '$opt'" });
956 my $repl_conf = PVE
::ReplicationConfig-
>new();
957 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
958 my $check_replication = sub {
960 return if !$is_replicated;
961 my $volid = $drive->{file
};
962 return if !$volid || !($drive->{replicate
}//1);
963 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
964 my ($storeid, $format);
965 if ($volid =~ $NEW_DISK_RE) {
967 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
969 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
970 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
972 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
973 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
974 return if $scfg->{shared
};
975 die "cannot add non-replicatable volume to a replicated VM\n";
978 foreach my $opt (keys %$param) {
979 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
981 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
982 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
983 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
984 $check_replication->($drive);
985 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
986 } elsif ($opt =~ m/^net(\d+)$/) {
988 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
989 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
993 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
995 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
997 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1001 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1003 die "checksum missmatch (file change by other user?)\n"
1004 if $digest && $digest ne $conf->{digest
};
1006 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1008 foreach my $opt (keys %$revert) {
1009 if (defined($conf->{$opt})) {
1010 $param->{$opt} = $conf->{$opt};
1011 } elsif (defined($conf->{pending
}->{$opt})) {
1016 if ($param->{memory
} || defined($param->{balloon
})) {
1017 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1018 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1020 die "balloon value too large (must be smaller than assigned memory)\n"
1021 if $balloon && $balloon > $maxmem;
1024 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1028 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1030 # write updates to pending section
1032 my $modified = {}; # record what $option we modify
1034 foreach my $opt (@delete) {
1035 $modified->{$opt} = 1;
1036 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1037 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1038 warn "cannot delete '$opt' - not set in current configuration!\n";
1039 $modified->{$opt} = 0;
1043 if ($opt =~ m/^unused/) {
1044 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1045 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1046 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1047 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1048 delete $conf->{$opt};
1049 PVE
::QemuConfig-
>write_config($vmid, $conf);
1051 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1052 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1053 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1054 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1055 if defined($conf->{pending
}->{$opt});
1056 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1057 PVE
::QemuConfig-
>write_config($vmid, $conf);
1059 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1060 PVE
::QemuConfig-
>write_config($vmid, $conf);
1064 foreach my $opt (keys %$param) { # add/change
1065 $modified->{$opt} = 1;
1066 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1067 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1069 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1070 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1071 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1072 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1074 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1076 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1077 if defined($conf->{pending
}->{$opt});
1079 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1081 $conf->{pending
}->{$opt} = $param->{$opt};
1083 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1084 PVE
::QemuConfig-
>write_config($vmid, $conf);
1087 # remove pending changes when nothing changed
1088 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1089 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1090 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1092 return if !scalar(keys %{$conf->{pending
}});
1094 my $running = PVE
::QemuServer
::check_running
($vmid);
1096 # apply pending changes
1098 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1102 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1103 raise_param_exc
($errors) if scalar(keys %$errors);
1105 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1115 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1117 if ($background_delay) {
1119 # Note: It would be better to do that in the Event based HTTPServer
1120 # to avoid blocking call to sleep.
1122 my $end_time = time() + $background_delay;
1124 my $task = PVE
::Tools
::upid_decode
($upid);
1127 while (time() < $end_time) {
1128 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1130 sleep(1); # this gets interrupted when child process ends
1134 my $status = PVE
::Tools
::upid_read_status
($upid);
1135 return undef if $status eq 'OK';
1144 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1147 my $vm_config_perm_list = [
1152 'VM.Config.Network',
1154 'VM.Config.Options',
1157 __PACKAGE__-
>register_method({
1158 name
=> 'update_vm_async',
1159 path
=> '{vmid}/config',
1163 description
=> "Set virtual machine options (asynchrounous API).",
1165 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1168 additionalProperties
=> 0,
1169 properties
=> PVE
::QemuServer
::json_config_properties
(
1171 node
=> get_standard_option
('pve-node'),
1172 vmid
=> get_standard_option
('pve-vmid'),
1173 skiplock
=> get_standard_option
('skiplock'),
1175 type
=> 'string', format
=> 'pve-configid-list',
1176 description
=> "A list of settings you want to delete.",
1180 type
=> 'string', format
=> 'pve-configid-list',
1181 description
=> "Revert a pending change.",
1186 description
=> $opt_force_description,
1188 requires
=> 'delete',
1192 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1196 background_delay
=> {
1198 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1209 code
=> $update_vm_api,
1212 __PACKAGE__-
>register_method({
1213 name
=> 'update_vm',
1214 path
=> '{vmid}/config',
1218 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1220 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1223 additionalProperties
=> 0,
1224 properties
=> PVE
::QemuServer
::json_config_properties
(
1226 node
=> get_standard_option
('pve-node'),
1227 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1228 skiplock
=> get_standard_option
('skiplock'),
1230 type
=> 'string', format
=> 'pve-configid-list',
1231 description
=> "A list of settings you want to delete.",
1235 type
=> 'string', format
=> 'pve-configid-list',
1236 description
=> "Revert a pending change.",
1241 description
=> $opt_force_description,
1243 requires
=> 'delete',
1247 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1253 returns
=> { type
=> 'null' },
1256 &$update_vm_api($param, 1);
1262 __PACKAGE__-
>register_method({
1263 name
=> 'destroy_vm',
1268 description
=> "Destroy the vm (also delete all used/owned volumes).",
1270 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1273 additionalProperties
=> 0,
1275 node
=> get_standard_option
('pve-node'),
1276 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1277 skiplock
=> get_standard_option
('skiplock'),
1286 my $rpcenv = PVE
::RPCEnvironment
::get
();
1288 my $authuser = $rpcenv->get_user();
1290 my $vmid = $param->{vmid
};
1292 my $skiplock = $param->{skiplock
};
1293 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1294 if $skiplock && $authuser ne 'root@pam';
1297 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1299 my $storecfg = PVE
::Storage
::config
();
1301 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1303 die "unable to remove VM $vmid - used in HA resources\n"
1304 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1306 # do not allow destroy if there are replication jobs
1307 my $repl_conf = PVE
::ReplicationConfig-
>new();
1308 $repl_conf->check_for_existing_jobs($vmid);
1310 # early tests (repeat after locking)
1311 die "VM $vmid is running - destroy failed\n"
1312 if PVE
::QemuServer
::check_running
($vmid);
1317 syslog
('info', "destroy VM $vmid: $upid\n");
1319 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1321 PVE
::AccessControl
::remove_vm_access
($vmid);
1323 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1326 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1329 __PACKAGE__-
>register_method({
1331 path
=> '{vmid}/unlink',
1335 description
=> "Unlink/delete disk images.",
1337 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1340 additionalProperties
=> 0,
1342 node
=> get_standard_option
('pve-node'),
1343 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1345 type
=> 'string', format
=> 'pve-configid-list',
1346 description
=> "A list of disk IDs you want to delete.",
1350 description
=> $opt_force_description,
1355 returns
=> { type
=> 'null'},
1359 $param->{delete} = extract_param
($param, 'idlist');
1361 __PACKAGE__-
>update_vm($param);
1368 __PACKAGE__-
>register_method({
1370 path
=> '{vmid}/vncproxy',
1374 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1376 description
=> "Creates a TCP VNC proxy connections.",
1378 additionalProperties
=> 0,
1380 node
=> get_standard_option
('pve-node'),
1381 vmid
=> get_standard_option
('pve-vmid'),
1385 description
=> "starts websockify instead of vncproxy",
1390 additionalProperties
=> 0,
1392 user
=> { type
=> 'string' },
1393 ticket
=> { type
=> 'string' },
1394 cert
=> { type
=> 'string' },
1395 port
=> { type
=> 'integer' },
1396 upid
=> { type
=> 'string' },
1402 my $rpcenv = PVE
::RPCEnvironment
::get
();
1404 my $authuser = $rpcenv->get_user();
1406 my $vmid = $param->{vmid
};
1407 my $node = $param->{node
};
1408 my $websocket = $param->{websocket
};
1410 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1412 my $authpath = "/vms/$vmid";
1414 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1416 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1419 my ($remip, $family);
1422 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1423 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1424 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1425 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1427 $family = PVE
::Tools
::get_host_address_family
($node);
1430 my $port = PVE
::Tools
::next_vnc_port
($family);
1437 syslog
('info', "starting vnc proxy $upid\n");
1441 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1443 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1445 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1446 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1447 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1448 '-timeout', $timeout, '-authpath', $authpath,
1449 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1450 PVE
::Tools
::run_command
($cmd);
1453 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1455 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1457 my $sock = IO
::Socket
::IP-
>new(
1462 GetAddrInfoFlags
=> 0,
1463 ) or die "failed to create socket: $!\n";
1464 # Inside the worker we shouldn't have any previous alarms
1465 # running anyway...:
1467 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1469 accept(my $cli, $sock) or die "connection failed: $!\n";
1472 if (PVE
::Tools
::run_command
($cmd,
1473 output
=> '>&'.fileno($cli),
1474 input
=> '<&'.fileno($cli),
1477 die "Failed to run vncproxy.\n";
1484 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1486 PVE
::Tools
::wait_for_vnc_port
($port);
1497 __PACKAGE__-
>register_method({
1498 name
=> 'vncwebsocket',
1499 path
=> '{vmid}/vncwebsocket',
1502 description
=> "You also need to pass a valid ticket (vncticket).",
1503 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1505 description
=> "Opens a weksocket for VNC traffic.",
1507 additionalProperties
=> 0,
1509 node
=> get_standard_option
('pve-node'),
1510 vmid
=> get_standard_option
('pve-vmid'),
1512 description
=> "Ticket from previous call to vncproxy.",
1517 description
=> "Port number returned by previous vncproxy call.",
1527 port
=> { type
=> 'string' },
1533 my $rpcenv = PVE
::RPCEnvironment
::get
();
1535 my $authuser = $rpcenv->get_user();
1537 my $vmid = $param->{vmid
};
1538 my $node = $param->{node
};
1540 my $authpath = "/vms/$vmid";
1542 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1544 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1546 # Note: VNC ports are acessible from outside, so we do not gain any
1547 # security if we verify that $param->{port} belongs to VM $vmid. This
1548 # check is done by verifying the VNC ticket (inside VNC protocol).
1550 my $port = $param->{port
};
1552 return { port
=> $port };
1555 __PACKAGE__-
>register_method({
1556 name
=> 'spiceproxy',
1557 path
=> '{vmid}/spiceproxy',
1562 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1564 description
=> "Returns a SPICE configuration to connect to the VM.",
1566 additionalProperties
=> 0,
1568 node
=> get_standard_option
('pve-node'),
1569 vmid
=> get_standard_option
('pve-vmid'),
1570 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1573 returns
=> get_standard_option
('remote-viewer-config'),
1577 my $rpcenv = PVE
::RPCEnvironment
::get
();
1579 my $authuser = $rpcenv->get_user();
1581 my $vmid = $param->{vmid
};
1582 my $node = $param->{node
};
1583 my $proxy = $param->{proxy
};
1585 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1586 my $title = "VM $vmid";
1587 $title .= " - ". $conf->{name
} if $conf->{name
};
1589 my $port = PVE
::QemuServer
::spice_port
($vmid);
1591 my ($ticket, undef, $remote_viewer_config) =
1592 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1594 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1595 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1597 return $remote_viewer_config;
1600 __PACKAGE__-
>register_method({
1602 path
=> '{vmid}/status',
1605 description
=> "Directory index",
1610 additionalProperties
=> 0,
1612 node
=> get_standard_option
('pve-node'),
1613 vmid
=> get_standard_option
('pve-vmid'),
1621 subdir
=> { type
=> 'string' },
1624 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1630 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1633 { subdir
=> 'current' },
1634 { subdir
=> 'start' },
1635 { subdir
=> 'stop' },
1641 __PACKAGE__-
>register_method({
1642 name
=> 'vm_status',
1643 path
=> '{vmid}/status/current',
1646 protected
=> 1, # qemu pid files are only readable by root
1647 description
=> "Get virtual machine status.",
1649 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1652 additionalProperties
=> 0,
1654 node
=> get_standard_option
('pve-node'),
1655 vmid
=> get_standard_option
('pve-vmid'),
1658 returns
=> { type
=> 'object' },
1663 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1665 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1666 my $status = $vmstatus->{$param->{vmid
}};
1668 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1670 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1675 __PACKAGE__-
>register_method({
1677 path
=> '{vmid}/status/start',
1681 description
=> "Start virtual machine.",
1683 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1686 additionalProperties
=> 0,
1688 node
=> get_standard_option
('pve-node'),
1689 vmid
=> get_standard_option
('pve-vmid',
1690 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1691 skiplock
=> get_standard_option
('skiplock'),
1692 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1693 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1696 enum
=> ['secure', 'insecure'],
1697 description
=> "Migration traffic is encrypted using an SSH " .
1698 "tunnel by default. On secure, completely private networks " .
1699 "this can be disabled to increase performance.",
1702 migration_network
=> {
1703 type
=> 'string', format
=> 'CIDR',
1704 description
=> "CIDR of the (sub) network that is used for migration.",
1707 machine
=> get_standard_option
('pve-qm-machine'),
1709 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1721 my $rpcenv = PVE
::RPCEnvironment
::get
();
1723 my $authuser = $rpcenv->get_user();
1725 my $node = extract_param
($param, 'node');
1727 my $vmid = extract_param
($param, 'vmid');
1729 my $machine = extract_param
($param, 'machine');
1731 my $stateuri = extract_param
($param, 'stateuri');
1732 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1733 if $stateuri && $authuser ne 'root@pam';
1735 my $skiplock = extract_param
($param, 'skiplock');
1736 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1737 if $skiplock && $authuser ne 'root@pam';
1739 my $migratedfrom = extract_param
($param, 'migratedfrom');
1740 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1741 if $migratedfrom && $authuser ne 'root@pam';
1743 my $migration_type = extract_param
($param, 'migration_type');
1744 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1745 if $migration_type && $authuser ne 'root@pam';
1747 my $migration_network = extract_param
($param, 'migration_network');
1748 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1749 if $migration_network && $authuser ne 'root@pam';
1751 my $targetstorage = extract_param
($param, 'targetstorage');
1752 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1753 if $targetstorage && $authuser ne 'root@pam';
1755 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1756 if $targetstorage && !$migratedfrom;
1758 # read spice ticket from STDIN
1760 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1761 if (defined(my $line = <>)) {
1763 $spice_ticket = $line;
1767 PVE
::Cluster
::check_cfs_quorum
();
1769 my $storecfg = PVE
::Storage
::config
();
1771 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1772 $rpcenv->{type
} ne 'ha') {
1777 my $service = "vm:$vmid";
1779 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1781 print "Requesting HA start for VM $vmid\n";
1783 PVE
::Tools
::run_command
($cmd);
1788 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1795 syslog
('info', "start VM $vmid: $upid\n");
1797 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1798 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1803 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1807 __PACKAGE__-
>register_method({
1809 path
=> '{vmid}/status/stop',
1813 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1814 "is akin to pulling the power plug of a running computer and may damage the VM data",
1816 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1819 additionalProperties
=> 0,
1821 node
=> get_standard_option
('pve-node'),
1822 vmid
=> get_standard_option
('pve-vmid',
1823 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1824 skiplock
=> get_standard_option
('skiplock'),
1825 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1827 description
=> "Wait maximal timeout seconds.",
1833 description
=> "Do not deactivate storage volumes.",
1846 my $rpcenv = PVE
::RPCEnvironment
::get
();
1848 my $authuser = $rpcenv->get_user();
1850 my $node = extract_param
($param, 'node');
1852 my $vmid = extract_param
($param, 'vmid');
1854 my $skiplock = extract_param
($param, 'skiplock');
1855 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1856 if $skiplock && $authuser ne 'root@pam';
1858 my $keepActive = extract_param
($param, 'keepActive');
1859 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1860 if $keepActive && $authuser ne 'root@pam';
1862 my $migratedfrom = extract_param
($param, 'migratedfrom');
1863 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1864 if $migratedfrom && $authuser ne 'root@pam';
1867 my $storecfg = PVE
::Storage
::config
();
1869 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1874 my $service = "vm:$vmid";
1876 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1878 print "Requesting HA stop for VM $vmid\n";
1880 PVE
::Tools
::run_command
($cmd);
1885 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1891 syslog
('info', "stop VM $vmid: $upid\n");
1893 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1894 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1899 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1903 __PACKAGE__-
>register_method({
1905 path
=> '{vmid}/status/reset',
1909 description
=> "Reset virtual machine.",
1911 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1914 additionalProperties
=> 0,
1916 node
=> get_standard_option
('pve-node'),
1917 vmid
=> get_standard_option
('pve-vmid',
1918 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1919 skiplock
=> get_standard_option
('skiplock'),
1928 my $rpcenv = PVE
::RPCEnvironment
::get
();
1930 my $authuser = $rpcenv->get_user();
1932 my $node = extract_param
($param, 'node');
1934 my $vmid = extract_param
($param, 'vmid');
1936 my $skiplock = extract_param
($param, 'skiplock');
1937 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1938 if $skiplock && $authuser ne 'root@pam';
1940 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1945 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1950 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1953 __PACKAGE__-
>register_method({
1954 name
=> 'vm_shutdown',
1955 path
=> '{vmid}/status/shutdown',
1959 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1960 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1962 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1965 additionalProperties
=> 0,
1967 node
=> get_standard_option
('pve-node'),
1968 vmid
=> get_standard_option
('pve-vmid',
1969 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1970 skiplock
=> get_standard_option
('skiplock'),
1972 description
=> "Wait maximal timeout seconds.",
1978 description
=> "Make sure the VM stops.",
1984 description
=> "Do not deactivate storage volumes.",
1997 my $rpcenv = PVE
::RPCEnvironment
::get
();
1999 my $authuser = $rpcenv->get_user();
2001 my $node = extract_param
($param, 'node');
2003 my $vmid = extract_param
($param, 'vmid');
2005 my $skiplock = extract_param
($param, 'skiplock');
2006 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2007 if $skiplock && $authuser ne 'root@pam';
2009 my $keepActive = extract_param
($param, 'keepActive');
2010 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2011 if $keepActive && $authuser ne 'root@pam';
2013 my $storecfg = PVE
::Storage
::config
();
2017 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2018 # otherwise, we will infer a shutdown command, but run into the timeout,
2019 # then when the vm is resumed, it will instantly shutdown
2021 # checking the qmp status here to get feedback to the gui/cli/api
2022 # and the status query should not take too long
2025 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2029 if (!$err && $qmpstatus->{status
} eq "paused") {
2030 if ($param->{forceStop
}) {
2031 warn "VM is paused - stop instead of shutdown\n";
2034 die "VM is paused - cannot shutdown\n";
2038 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2039 ($rpcenv->{type
} ne 'ha')) {
2044 my $service = "vm:$vmid";
2046 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2048 print "Requesting HA stop for VM $vmid\n";
2050 PVE
::Tools
::run_command
($cmd);
2055 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2062 syslog
('info', "shutdown VM $vmid: $upid\n");
2064 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2065 $shutdown, $param->{forceStop
}, $keepActive);
2070 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2074 __PACKAGE__-
>register_method({
2075 name
=> 'vm_suspend',
2076 path
=> '{vmid}/status/suspend',
2080 description
=> "Suspend virtual machine.",
2082 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2085 additionalProperties
=> 0,
2087 node
=> get_standard_option
('pve-node'),
2088 vmid
=> get_standard_option
('pve-vmid',
2089 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2090 skiplock
=> get_standard_option
('skiplock'),
2099 my $rpcenv = PVE
::RPCEnvironment
::get
();
2101 my $authuser = $rpcenv->get_user();
2103 my $node = extract_param
($param, 'node');
2105 my $vmid = extract_param
($param, 'vmid');
2107 my $skiplock = extract_param
($param, 'skiplock');
2108 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2109 if $skiplock && $authuser ne 'root@pam';
2111 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2116 syslog
('info', "suspend VM $vmid: $upid\n");
2118 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2123 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2126 __PACKAGE__-
>register_method({
2127 name
=> 'vm_resume',
2128 path
=> '{vmid}/status/resume',
2132 description
=> "Resume virtual machine.",
2134 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2137 additionalProperties
=> 0,
2139 node
=> get_standard_option
('pve-node'),
2140 vmid
=> get_standard_option
('pve-vmid',
2141 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2142 skiplock
=> get_standard_option
('skiplock'),
2143 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2153 my $rpcenv = PVE
::RPCEnvironment
::get
();
2155 my $authuser = $rpcenv->get_user();
2157 my $node = extract_param
($param, 'node');
2159 my $vmid = extract_param
($param, 'vmid');
2161 my $skiplock = extract_param
($param, 'skiplock');
2162 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2163 if $skiplock && $authuser ne 'root@pam';
2165 my $nocheck = extract_param
($param, 'nocheck');
2167 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2172 syslog
('info', "resume VM $vmid: $upid\n");
2174 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2179 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2182 __PACKAGE__-
>register_method({
2183 name
=> 'vm_sendkey',
2184 path
=> '{vmid}/sendkey',
2188 description
=> "Send key event to virtual machine.",
2190 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2193 additionalProperties
=> 0,
2195 node
=> get_standard_option
('pve-node'),
2196 vmid
=> get_standard_option
('pve-vmid',
2197 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2198 skiplock
=> get_standard_option
('skiplock'),
2200 description
=> "The key (qemu monitor encoding).",
2205 returns
=> { type
=> 'null'},
2209 my $rpcenv = PVE
::RPCEnvironment
::get
();
2211 my $authuser = $rpcenv->get_user();
2213 my $node = extract_param
($param, 'node');
2215 my $vmid = extract_param
($param, 'vmid');
2217 my $skiplock = extract_param
($param, 'skiplock');
2218 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2219 if $skiplock && $authuser ne 'root@pam';
2221 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2226 __PACKAGE__-
>register_method({
2227 name
=> 'vm_feature',
2228 path
=> '{vmid}/feature',
2232 description
=> "Check if feature for virtual machine is available.",
2234 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2237 additionalProperties
=> 0,
2239 node
=> get_standard_option
('pve-node'),
2240 vmid
=> get_standard_option
('pve-vmid'),
2242 description
=> "Feature to check.",
2244 enum
=> [ 'snapshot', 'clone', 'copy' ],
2246 snapname
=> get_standard_option
('pve-snapshot-name', {
2254 hasFeature
=> { type
=> 'boolean' },
2257 items
=> { type
=> 'string' },
2264 my $node = extract_param
($param, 'node');
2266 my $vmid = extract_param
($param, 'vmid');
2268 my $snapname = extract_param
($param, 'snapname');
2270 my $feature = extract_param
($param, 'feature');
2272 my $running = PVE
::QemuServer
::check_running
($vmid);
2274 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2277 my $snap = $conf->{snapshots
}->{$snapname};
2278 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2281 my $storecfg = PVE
::Storage
::config
();
2283 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2284 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2287 hasFeature
=> $hasFeature,
2288 nodes
=> [ keys %$nodelist ],
2292 __PACKAGE__-
>register_method({
2294 path
=> '{vmid}/clone',
2298 description
=> "Create a copy of virtual machine/template.",
2300 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2301 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2302 "'Datastore.AllocateSpace' on any used storage.",
2305 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2307 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2308 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2313 additionalProperties
=> 0,
2315 node
=> get_standard_option
('pve-node'),
2316 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2317 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2320 type
=> 'string', format
=> 'dns-name',
2321 description
=> "Set a name for the new VM.",
2326 description
=> "Description for the new VM.",
2330 type
=> 'string', format
=> 'pve-poolid',
2331 description
=> "Add the new VM to the specified pool.",
2333 snapname
=> get_standard_option
('pve-snapshot-name', {
2336 storage
=> get_standard_option
('pve-storage-id', {
2337 description
=> "Target storage for full clone.",
2342 description
=> "Target format for file storage.",
2346 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2351 description
=> "Create a full copy of all disk. This is always done when " .
2352 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2355 target
=> get_standard_option
('pve-node', {
2356 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2367 my $rpcenv = PVE
::RPCEnvironment
::get
();
2369 my $authuser = $rpcenv->get_user();
2371 my $node = extract_param
($param, 'node');
2373 my $vmid = extract_param
($param, 'vmid');
2375 my $newid = extract_param
($param, 'newid');
2377 my $pool = extract_param
($param, 'pool');
2379 if (defined($pool)) {
2380 $rpcenv->check_pool_exist($pool);
2383 my $snapname = extract_param
($param, 'snapname');
2385 my $storage = extract_param
($param, 'storage');
2387 my $format = extract_param
($param, 'format');
2389 my $target = extract_param
($param, 'target');
2391 my $localnode = PVE
::INotify
::nodename
();
2393 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2395 PVE
::Cluster
::check_node_exists
($target) if $target;
2397 my $storecfg = PVE
::Storage
::config
();
2400 # check if storage is enabled on local node
2401 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2403 # check if storage is available on target node
2404 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2405 # clone only works if target storage is shared
2406 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2407 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2411 PVE
::Cluster
::check_cfs_quorum
();
2413 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2415 # exclusive lock if VM is running - else shared lock is enough;
2416 my $shared_lock = $running ?
0 : 1;
2420 # do all tests after lock
2421 # we also try to do all tests before we fork the worker
2423 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2425 PVE
::QemuConfig-
>check_lock($conf);
2427 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2429 die "unexpected state change\n" if $verify_running != $running;
2431 die "snapshot '$snapname' does not exist\n"
2432 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2434 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2436 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2438 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2440 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2442 die "unable to create VM $newid: config file already exists\n"
2445 my $newconf = { lock => 'clone' };
2450 foreach my $opt (keys %$oldconf) {
2451 my $value = $oldconf->{$opt};
2453 # do not copy snapshot related info
2454 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2455 $opt eq 'vmstate' || $opt eq 'snapstate';
2457 # no need to copy unused images, because VMID(owner) changes anyways
2458 next if $opt =~ m/^unused\d+$/;
2460 # always change MAC! address
2461 if ($opt =~ m/^net(\d+)$/) {
2462 my $net = PVE
::QemuServer
::parse_net
($value);
2463 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2464 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2465 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2466 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2467 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2468 die "unable to parse drive options for '$opt'\n" if !$drive;
2469 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2470 $newconf->{$opt} = $value; # simply copy configuration
2472 if ($param->{full
}) {
2473 die "Full clone feature is not supported for drive '$opt'\n"
2474 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2475 $fullclone->{$opt} = 1;
2477 # not full means clone instead of copy
2478 die "Linked clone feature is not supported for drive '$opt'\n"
2479 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2481 $drives->{$opt} = $drive;
2482 push @$vollist, $drive->{file
};
2485 # copy everything else
2486 $newconf->{$opt} = $value;
2490 # auto generate a new uuid
2491 my ($uuid, $uuid_str);
2492 UUID
::generate
($uuid);
2493 UUID
::unparse
($uuid, $uuid_str);
2494 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2495 $smbios1->{uuid
} = $uuid_str;
2496 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2498 delete $newconf->{template
};
2500 if ($param->{name
}) {
2501 $newconf->{name
} = $param->{name
};
2503 if ($oldconf->{name
}) {
2504 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2506 $newconf->{name
} = "Copy-of-VM-$vmid";
2510 if ($param->{description
}) {
2511 $newconf->{description
} = $param->{description
};
2514 # create empty/temp config - this fails if VM already exists on other node
2515 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2520 my $newvollist = [];
2527 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2529 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2531 my $total_jobs = scalar(keys %{$drives});
2534 foreach my $opt (keys %$drives) {
2535 my $drive = $drives->{$opt};
2536 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2538 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2539 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2540 $jobs, $skipcomplete, $oldconf->{agent
});
2542 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2544 PVE
::QemuConfig-
>write_config($newid, $newconf);
2548 delete $newconf->{lock};
2549 PVE
::QemuConfig-
>write_config($newid, $newconf);
2552 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2553 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2554 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2556 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2557 die "Failed to move config to node '$target' - rename failed: $!\n"
2558 if !rename($conffile, $newconffile);
2561 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2566 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2568 sleep 1; # some storage like rbd need to wait before release volume - really?
2570 foreach my $volid (@$newvollist) {
2571 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2574 die "clone failed: $err";
2580 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2582 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2585 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2586 # Aquire exclusive lock lock for $newid
2587 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2592 __PACKAGE__-
>register_method({
2593 name
=> 'move_vm_disk',
2594 path
=> '{vmid}/move_disk',
2598 description
=> "Move volume to different storage.",
2600 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2602 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2603 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2607 additionalProperties
=> 0,
2609 node
=> get_standard_option
('pve-node'),
2610 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2613 description
=> "The disk you want to move.",
2614 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2616 storage
=> get_standard_option
('pve-storage-id', {
2617 description
=> "Target storage.",
2618 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2622 description
=> "Target Format.",
2623 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2628 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2634 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2642 description
=> "the task ID.",
2647 my $rpcenv = PVE
::RPCEnvironment
::get
();
2649 my $authuser = $rpcenv->get_user();
2651 my $node = extract_param
($param, 'node');
2653 my $vmid = extract_param
($param, 'vmid');
2655 my $digest = extract_param
($param, 'digest');
2657 my $disk = extract_param
($param, 'disk');
2659 my $storeid = extract_param
($param, 'storage');
2661 my $format = extract_param
($param, 'format');
2663 my $storecfg = PVE
::Storage
::config
();
2665 my $updatefn = sub {
2667 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2669 PVE
::QemuConfig-
>check_lock($conf);
2671 die "checksum missmatch (file change by other user?)\n"
2672 if $digest && $digest ne $conf->{digest
};
2674 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2676 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2678 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2680 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2683 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2684 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2688 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2689 (!$format || !$oldfmt || $oldfmt eq $format);
2691 # this only checks snapshots because $disk is passed!
2692 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2693 die "you can't move a disk with snapshots and delete the source\n"
2694 if $snapshotted && $param->{delete};
2696 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2698 my $running = PVE
::QemuServer
::check_running
($vmid);
2700 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2704 my $newvollist = [];
2710 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2712 warn "moving disk with snapshots, snapshots will not be moved!\n"
2715 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2716 $vmid, $storeid, $format, 1, $newvollist);
2718 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2720 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2722 # convert moved disk to base if part of template
2723 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2724 if PVE
::QemuConfig-
>is_template($conf);
2726 PVE
::QemuConfig-
>write_config($vmid, $conf);
2729 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2730 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2737 foreach my $volid (@$newvollist) {
2738 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2741 die "storage migration failed: $err";
2744 if ($param->{delete}) {
2746 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2747 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2753 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2756 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2759 __PACKAGE__-
>register_method({
2760 name
=> 'migrate_vm',
2761 path
=> '{vmid}/migrate',
2765 description
=> "Migrate virtual machine. Creates a new migration task.",
2767 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2770 additionalProperties
=> 0,
2772 node
=> get_standard_option
('pve-node'),
2773 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2774 target
=> get_standard_option
('pve-node', {
2775 description
=> "Target node.",
2776 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2780 description
=> "Use online/live migration.",
2785 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2790 enum
=> ['secure', 'insecure'],
2791 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2794 migration_network
=> {
2795 type
=> 'string', format
=> 'CIDR',
2796 description
=> "CIDR of the (sub) network that is used for migration.",
2799 "with-local-disks" => {
2801 description
=> "Enable live storage migration for local disk",
2804 targetstorage
=> get_standard_option
('pve-storage-id', {
2805 description
=> "Default target storage.",
2807 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2813 description
=> "the task ID.",
2818 my $rpcenv = PVE
::RPCEnvironment
::get
();
2820 my $authuser = $rpcenv->get_user();
2822 my $target = extract_param
($param, 'target');
2824 my $localnode = PVE
::INotify
::nodename
();
2825 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2827 PVE
::Cluster
::check_cfs_quorum
();
2829 PVE
::Cluster
::check_node_exists
($target);
2831 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2833 my $vmid = extract_param
($param, 'vmid');
2835 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2836 if !$param->{online
} && $param->{targetstorage
};
2838 raise_param_exc
({ force
=> "Only root may use this option." })
2839 if $param->{force
} && $authuser ne 'root@pam';
2841 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2842 if $param->{migration_type
} && $authuser ne 'root@pam';
2844 # allow root only until better network permissions are available
2845 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2846 if $param->{migration_network
} && $authuser ne 'root@pam';
2849 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2851 # try to detect errors early
2853 PVE
::QemuConfig-
>check_lock($conf);
2855 if (PVE
::QemuServer
::check_running
($vmid)) {
2856 die "cant migrate running VM without --online\n"
2857 if !$param->{online
};
2860 my $storecfg = PVE
::Storage
::config
();
2862 if( $param->{targetstorage
}) {
2863 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2865 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2868 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2873 my $service = "vm:$vmid";
2875 my $cmd = ['ha-manager', 'migrate', $service, $target];
2877 print "Requesting HA migration for VM $vmid to node $target\n";
2879 PVE
::Tools
::run_command
($cmd);
2884 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2889 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2893 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2896 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2901 __PACKAGE__-
>register_method({
2903 path
=> '{vmid}/monitor',
2907 description
=> "Execute Qemu monitor commands.",
2909 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2910 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2913 additionalProperties
=> 0,
2915 node
=> get_standard_option
('pve-node'),
2916 vmid
=> get_standard_option
('pve-vmid'),
2919 description
=> "The monitor command.",
2923 returns
=> { type
=> 'string'},
2927 my $rpcenv = PVE
::RPCEnvironment
::get
();
2928 my $authuser = $rpcenv->get_user();
2931 my $command = shift;
2932 return $command =~ m/^\s*info(\s+|$)/
2933 || $command =~ m/^\s*help\s*$/;
2936 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2937 if !&$is_ro($param->{command
});
2939 my $vmid = $param->{vmid
};
2941 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2945 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2947 $res = "ERROR: $@" if $@;
2952 my $guest_agent_commands = [
2960 'network-get-interfaces',
2963 'get-memory-blocks',
2964 'get-memory-block-info',
2971 __PACKAGE__-
>register_method({
2973 path
=> '{vmid}/agent',
2977 description
=> "Execute Qemu Guest Agent commands.",
2979 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2982 additionalProperties
=> 0,
2984 node
=> get_standard_option
('pve-node'),
2985 vmid
=> get_standard_option
('pve-vmid', {
2986 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2989 description
=> "The QGA command.",
2990 enum
=> $guest_agent_commands,
2996 description
=> "Returns an object with a single `result` property. The type of that
2997 property depends on the executed command.",
3002 my $vmid = $param->{vmid
};
3004 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3006 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3007 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3009 my $cmd = $param->{command
};
3011 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3013 return { result
=> $res };
3016 __PACKAGE__-
>register_method({
3017 name
=> 'resize_vm',
3018 path
=> '{vmid}/resize',
3022 description
=> "Extend volume size.",
3024 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3027 additionalProperties
=> 0,
3029 node
=> get_standard_option
('pve-node'),
3030 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3031 skiplock
=> get_standard_option
('skiplock'),
3034 description
=> "The disk you want to resize.",
3035 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3039 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3040 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.",
3044 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3050 returns
=> { type
=> 'null'},
3054 my $rpcenv = PVE
::RPCEnvironment
::get
();
3056 my $authuser = $rpcenv->get_user();
3058 my $node = extract_param
($param, 'node');
3060 my $vmid = extract_param
($param, 'vmid');
3062 my $digest = extract_param
($param, 'digest');
3064 my $disk = extract_param
($param, 'disk');
3066 my $sizestr = extract_param
($param, 'size');
3068 my $skiplock = extract_param
($param, 'skiplock');
3069 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3070 if $skiplock && $authuser ne 'root@pam';
3072 my $storecfg = PVE
::Storage
::config
();
3074 my $updatefn = sub {
3076 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3078 die "checksum missmatch (file change by other user?)\n"
3079 if $digest && $digest ne $conf->{digest
};
3080 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3082 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3084 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3086 my (undef, undef, undef, undef, undef, undef, $format) =
3087 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3089 die "can't resize volume: $disk if snapshot exists\n"
3090 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3092 my $volid = $drive->{file
};
3094 die "disk '$disk' has no associated volume\n" if !$volid;
3096 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3098 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3100 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3102 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3103 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3105 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3106 my ($ext, $newsize, $unit) = ($1, $2, $4);
3109 $newsize = $newsize * 1024;
3110 } elsif ($unit eq 'M') {
3111 $newsize = $newsize * 1024 * 1024;
3112 } elsif ($unit eq 'G') {
3113 $newsize = $newsize * 1024 * 1024 * 1024;
3114 } elsif ($unit eq 'T') {
3115 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3118 $newsize += $size if $ext;
3119 $newsize = int($newsize);
3121 die "shrinking disks is not supported\n" if $newsize < $size;
3123 return if $size == $newsize;
3125 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3127 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3129 $drive->{size
} = $newsize;
3130 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3132 PVE
::QemuConfig-
>write_config($vmid, $conf);
3135 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3139 __PACKAGE__-
>register_method({
3140 name
=> 'snapshot_list',
3141 path
=> '{vmid}/snapshot',
3143 description
=> "List all snapshots.",
3145 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3148 protected
=> 1, # qemu pid files are only readable by root
3150 additionalProperties
=> 0,
3152 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3153 node
=> get_standard_option
('pve-node'),
3162 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3167 my $vmid = $param->{vmid
};
3169 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3170 my $snaphash = $conf->{snapshots
} || {};
3174 foreach my $name (keys %$snaphash) {
3175 my $d = $snaphash->{$name};
3178 snaptime
=> $d->{snaptime
} || 0,
3179 vmstate
=> $d->{vmstate
} ?
1 : 0,
3180 description
=> $d->{description
} || '',
3182 $item->{parent
} = $d->{parent
} if $d->{parent
};
3183 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3187 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3188 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3189 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3191 push @$res, $current;
3196 __PACKAGE__-
>register_method({
3198 path
=> '{vmid}/snapshot',
3202 description
=> "Snapshot a VM.",
3204 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3207 additionalProperties
=> 0,
3209 node
=> get_standard_option
('pve-node'),
3210 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3211 snapname
=> get_standard_option
('pve-snapshot-name'),
3215 description
=> "Save the vmstate",
3220 description
=> "A textual description or comment.",
3226 description
=> "the task ID.",
3231 my $rpcenv = PVE
::RPCEnvironment
::get
();
3233 my $authuser = $rpcenv->get_user();
3235 my $node = extract_param
($param, 'node');
3237 my $vmid = extract_param
($param, 'vmid');
3239 my $snapname = extract_param
($param, 'snapname');
3241 die "unable to use snapshot name 'current' (reserved name)\n"
3242 if $snapname eq 'current';
3245 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3246 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3247 $param->{description
});
3250 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3253 __PACKAGE__-
>register_method({
3254 name
=> 'snapshot_cmd_idx',
3255 path
=> '{vmid}/snapshot/{snapname}',
3262 additionalProperties
=> 0,
3264 vmid
=> get_standard_option
('pve-vmid'),
3265 node
=> get_standard_option
('pve-node'),
3266 snapname
=> get_standard_option
('pve-snapshot-name'),
3275 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3282 push @$res, { cmd
=> 'rollback' };
3283 push @$res, { cmd
=> 'config' };
3288 __PACKAGE__-
>register_method({
3289 name
=> 'update_snapshot_config',
3290 path
=> '{vmid}/snapshot/{snapname}/config',
3294 description
=> "Update snapshot metadata.",
3296 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3299 additionalProperties
=> 0,
3301 node
=> get_standard_option
('pve-node'),
3302 vmid
=> get_standard_option
('pve-vmid'),
3303 snapname
=> get_standard_option
('pve-snapshot-name'),
3307 description
=> "A textual description or comment.",
3311 returns
=> { type
=> 'null' },
3315 my $rpcenv = PVE
::RPCEnvironment
::get
();
3317 my $authuser = $rpcenv->get_user();
3319 my $vmid = extract_param
($param, 'vmid');
3321 my $snapname = extract_param
($param, 'snapname');
3323 return undef if !defined($param->{description
});
3325 my $updatefn = sub {
3327 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3329 PVE
::QemuConfig-
>check_lock($conf);
3331 my $snap = $conf->{snapshots
}->{$snapname};
3333 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3335 $snap->{description
} = $param->{description
} if defined($param->{description
});
3337 PVE
::QemuConfig-
>write_config($vmid, $conf);
3340 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3345 __PACKAGE__-
>register_method({
3346 name
=> 'get_snapshot_config',
3347 path
=> '{vmid}/snapshot/{snapname}/config',
3350 description
=> "Get snapshot configuration",
3352 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3355 additionalProperties
=> 0,
3357 node
=> get_standard_option
('pve-node'),
3358 vmid
=> get_standard_option
('pve-vmid'),
3359 snapname
=> get_standard_option
('pve-snapshot-name'),
3362 returns
=> { type
=> "object" },
3366 my $rpcenv = PVE
::RPCEnvironment
::get
();
3368 my $authuser = $rpcenv->get_user();
3370 my $vmid = extract_param
($param, 'vmid');
3372 my $snapname = extract_param
($param, 'snapname');
3374 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3376 my $snap = $conf->{snapshots
}->{$snapname};
3378 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3383 __PACKAGE__-
>register_method({
3385 path
=> '{vmid}/snapshot/{snapname}/rollback',
3389 description
=> "Rollback VM state to specified snapshot.",
3391 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3394 additionalProperties
=> 0,
3396 node
=> get_standard_option
('pve-node'),
3397 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3398 snapname
=> get_standard_option
('pve-snapshot-name'),
3403 description
=> "the task ID.",
3408 my $rpcenv = PVE
::RPCEnvironment
::get
();
3410 my $authuser = $rpcenv->get_user();
3412 my $node = extract_param
($param, 'node');
3414 my $vmid = extract_param
($param, 'vmid');
3416 my $snapname = extract_param
($param, 'snapname');
3419 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3420 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3424 # hold migration lock, this makes sure that nobody create replication snapshots
3425 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3428 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3431 __PACKAGE__-
>register_method({
3432 name
=> 'delsnapshot',
3433 path
=> '{vmid}/snapshot/{snapname}',
3437 description
=> "Delete a VM snapshot.",
3439 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3442 additionalProperties
=> 0,
3444 node
=> get_standard_option
('pve-node'),
3445 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3446 snapname
=> get_standard_option
('pve-snapshot-name'),
3450 description
=> "For removal from config file, even if removing disk snapshots fails.",
3456 description
=> "the task ID.",
3461 my $rpcenv = PVE
::RPCEnvironment
::get
();
3463 my $authuser = $rpcenv->get_user();
3465 my $node = extract_param
($param, 'node');
3467 my $vmid = extract_param
($param, 'vmid');
3469 my $snapname = extract_param
($param, 'snapname');
3472 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3473 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3476 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3479 __PACKAGE__-
>register_method({
3481 path
=> '{vmid}/template',
3485 description
=> "Create a Template.",
3487 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3488 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3491 additionalProperties
=> 0,
3493 node
=> get_standard_option
('pve-node'),
3494 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3498 description
=> "If you want to convert only 1 disk to base image.",
3499 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3504 returns
=> { type
=> 'null'},
3508 my $rpcenv = PVE
::RPCEnvironment
::get
();
3510 my $authuser = $rpcenv->get_user();
3512 my $node = extract_param
($param, 'node');
3514 my $vmid = extract_param
($param, 'vmid');
3516 my $disk = extract_param
($param, 'disk');
3518 my $updatefn = sub {
3520 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3522 PVE
::QemuConfig-
>check_lock($conf);
3524 die "unable to create template, because VM contains snapshots\n"
3525 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3527 die "you can't convert a template to a template\n"
3528 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3530 die "you can't convert a VM to template if VM is running\n"
3531 if PVE
::QemuServer
::check_running
($vmid);
3534 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3537 $conf->{template
} = 1;
3538 PVE
::QemuConfig-
>write_config($vmid, $conf);
3540 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3543 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);