1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::ReplicationConfig
;
19 use PVE
::GuestHelpers
;
23 use PVE
::RPCEnvironment
;
24 use PVE
::AccessControl
;
28 use PVE
::API2
::Firewall
::VM
;
31 if (!$ENV{PVE_GENERATING_DOCS
}) {
32 require PVE
::HA
::Env
::PVE2
;
33 import PVE
::HA
::Env
::PVE2
;
34 require PVE
::HA
::Config
;
35 import PVE
::HA
::Config
;
39 use Data
::Dumper
; # fixme: remove
41 use base
qw(PVE::RESTHandler);
43 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
45 my $resolve_cdrom_alias = sub {
48 if (my $value = $param->{cdrom
}) {
49 $value .= ",media=cdrom" if $value !~ m/media=/;
50 $param->{ide2
} = $value;
51 delete $param->{cdrom
};
55 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
56 my $check_storage_access = sub {
57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
59 PVE
::QemuServer
::foreach_drive
($settings, sub {
60 my ($ds, $drive) = @_;
62 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
64 my $volid = $drive->{file
};
66 if (!$volid || $volid eq 'none') {
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
70 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
74 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
75 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
76 if !$scfg->{content
}->{images
};
78 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
83 my $check_storage_access_clone = sub {
84 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
88 PVE
::QemuServer
::foreach_drive
($conf, sub {
89 my ($ds, $drive) = @_;
91 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
93 my $volid = $drive->{file
};
95 return if !$volid || $volid eq 'none';
98 if ($volid eq 'cdrom') {
99 $rpcenv->check($authuser, "/", ['Sys.Console']);
101 # we simply allow access
102 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
103 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
104 $sharedvm = 0 if !$scfg->{shared
};
108 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
109 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
110 $sharedvm = 0 if !$scfg->{shared
};
112 $sid = $storage if $storage;
113 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
120 # Note: $pool is only needed when creating a VM, because pool permissions
121 # are automatically inherited if VM already exists inside a pool.
122 my $create_disks = sub {
123 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
130 my ($ds, $disk) = @_;
132 my $volid = $disk->{file
};
134 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
135 delete $disk->{size
};
136 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
137 } elsif ($volid =~ $NEW_DISK_RE) {
138 my ($storeid, $size) = ($2 || $default_storage, $3);
139 die "no storage ID specified (and no default storage)\n" if !$storeid;
140 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
141 my $fmt = $disk->{format
} || $defformat;
143 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
146 if ($ds eq 'efidisk0') {
147 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt);
149 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
151 push @$vollist, $volid;
152 $disk->{file
} = $volid;
153 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
154 delete $disk->{format
}; # no longer needed
155 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
158 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
160 my $volid_is_new = 1;
163 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
164 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
169 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
171 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
173 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
175 die "volume $volid does not exists\n" if !$size;
177 $disk->{size
} = $size;
180 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
184 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
186 # free allocated images on error
188 syslog
('err', "VM $vmid creating disks failed");
189 foreach my $volid (@$vollist) {
190 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
196 # modify vm config if everything went well
197 foreach my $ds (keys %$res) {
198 $conf->{$ds} = $res->{$ds};
215 my $memoryoptions = {
221 my $hwtypeoptions = {
233 my $generaloptions = {
240 'migrate_downtime' => 1,
241 'migrate_speed' => 1,
253 my $vmpoweroptions = {
262 my $check_vm_modify_config_perm = sub {
263 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
265 return 1 if $authuser eq 'root@pam';
267 foreach my $opt (@$key_list) {
268 # disk checks need to be done somewhere else
269 next if PVE
::QemuServer
::is_valid_drivename
($opt);
270 next if $opt eq 'cdrom';
271 next if $opt =~ m/^unused\d+$/;
273 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
274 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
275 } elsif ($memoryoptions->{$opt}) {
276 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
277 } elsif ($hwtypeoptions->{$opt}) {
278 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
279 } elsif ($generaloptions->{$opt}) {
280 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
281 # special case for startup since it changes host behaviour
282 if ($opt eq 'startup') {
283 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
285 } elsif ($vmpoweroptions->{$opt}) {
286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
287 } elsif ($diskoptions->{$opt}) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
289 } elsif ($opt =~ m/^net\d+$/) {
290 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
292 # catches usb\d+, hostpci\d+, args, lock, etc.
293 # new options will be checked here
294 die "only root can set '$opt' config\n";
301 __PACKAGE__-
>register_method({
305 description
=> "Virtual machine index (per node).",
307 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
311 protected
=> 1, # qemu pid files are only readable by root
313 additionalProperties
=> 0,
315 node
=> get_standard_option
('pve-node'),
319 description
=> "Determine the full status of active VMs.",
329 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
334 my $rpcenv = PVE
::RPCEnvironment
::get
();
335 my $authuser = $rpcenv->get_user();
337 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
340 foreach my $vmid (keys %$vmstatus) {
341 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
343 my $data = $vmstatus->{$vmid};
344 $data->{vmid
} = int($vmid);
353 __PACKAGE__-
>register_method({
357 description
=> "Create or restore a virtual machine.",
359 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
360 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
361 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
362 user
=> 'all', # check inside
367 additionalProperties
=> 0,
368 properties
=> PVE
::QemuServer
::json_config_properties
(
370 node
=> get_standard_option
('pve-node'),
371 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
373 description
=> "The backup file.",
377 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
379 storage
=> get_standard_option
('pve-storage-id', {
380 description
=> "Default storage.",
382 completion
=> \
&PVE
::QemuServer
::complete_storage
,
387 description
=> "Allow to overwrite existing VM.",
388 requires
=> 'archive',
393 description
=> "Assign a unique random ethernet address.",
394 requires
=> 'archive',
398 type
=> 'string', format
=> 'pve-poolid',
399 description
=> "Add the VM to the specified pool.",
409 my $rpcenv = PVE
::RPCEnvironment
::get
();
411 my $authuser = $rpcenv->get_user();
413 my $node = extract_param
($param, 'node');
415 my $vmid = extract_param
($param, 'vmid');
417 my $archive = extract_param
($param, 'archive');
419 my $storage = extract_param
($param, 'storage');
421 my $force = extract_param
($param, 'force');
423 my $unique = extract_param
($param, 'unique');
425 my $pool = extract_param
($param, 'pool');
427 my $filename = PVE
::QemuConfig-
>config_file($vmid);
429 my $storecfg = PVE
::Storage
::config
();
431 PVE
::Cluster
::check_cfs_quorum
();
433 if (defined($pool)) {
434 $rpcenv->check_pool_exist($pool);
437 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
438 if defined($storage);
440 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
442 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
444 } elsif ($archive && $force && (-f
$filename) &&
445 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
446 # OK: user has VM.Backup permissions, and want to restore an existing VM
452 &$resolve_cdrom_alias($param);
454 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
456 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
458 foreach my $opt (keys %$param) {
459 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
460 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
461 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
463 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
464 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
468 PVE
::QemuServer
::add_random_macs
($param);
470 my $keystr = join(' ', keys %$param);
471 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
473 if ($archive eq '-') {
474 die "pipe requires cli environment\n"
475 if $rpcenv->{type
} ne 'cli';
477 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
478 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
482 my $restorefn = sub {
483 my $vmlist = PVE
::Cluster
::get_vmlist
();
484 if ($vmlist->{ids
}->{$vmid}) {
485 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
486 if ($current_node eq $node) {
487 my $conf = PVE
::QemuConfig-
>load_config($vmid);
489 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
491 die "unable to restore vm $vmid - config file already exists\n"
494 die "unable to restore vm $vmid - vm is running\n"
495 if PVE
::QemuServer
::check_running
($vmid);
497 die "unable to restore vm $vmid - vm is a template\n"
498 if PVE
::QemuConfig-
>is_template($conf);
501 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
506 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
509 unique
=> $unique });
511 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
514 # ensure no old replication state are exists
515 PVE
::ReplicationState
::delete_guest_states
($vmid);
517 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
523 PVE
::Cluster
::check_vmid_unused
($vmid);
525 # ensure no old replication state are exists
526 PVE
::ReplicationState
::delete_guest_states
($vmid);
536 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
538 # try to be smart about bootdisk
539 my @disks = PVE
::QemuServer
::valid_drive_names
();
541 foreach my $ds (reverse @disks) {
542 next if !$conf->{$ds};
543 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
544 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
548 if (!$conf->{bootdisk
} && $firstdisk) {
549 $conf->{bootdisk
} = $firstdisk;
552 # auto generate uuid if user did not specify smbios1 option
553 if (!$conf->{smbios1
}) {
554 my ($uuid, $uuid_str);
555 UUID
::generate
($uuid);
556 UUID
::unparse
($uuid, $uuid_str);
557 $conf->{smbios1
} = "uuid=$uuid_str";
560 PVE
::QemuConfig-
>write_config($vmid, $conf);
566 foreach my $volid (@$vollist) {
567 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
570 die "create failed - $err";
573 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
576 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
579 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
582 __PACKAGE__-
>register_method({
587 description
=> "Directory index",
592 additionalProperties
=> 0,
594 node
=> get_standard_option
('pve-node'),
595 vmid
=> get_standard_option
('pve-vmid'),
603 subdir
=> { type
=> 'string' },
606 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
612 { subdir
=> 'config' },
613 { subdir
=> 'pending' },
614 { subdir
=> 'status' },
615 { subdir
=> 'unlink' },
616 { subdir
=> 'vncproxy' },
617 { subdir
=> 'migrate' },
618 { subdir
=> 'resize' },
619 { subdir
=> 'move' },
621 { subdir
=> 'rrddata' },
622 { subdir
=> 'monitor' },
623 { subdir
=> 'agent' },
624 { subdir
=> 'snapshot' },
625 { subdir
=> 'spiceproxy' },
626 { subdir
=> 'sendkey' },
627 { subdir
=> 'firewall' },
633 __PACKAGE__-
>register_method ({
634 subclass
=> "PVE::API2::Firewall::VM",
635 path
=> '{vmid}/firewall',
638 __PACKAGE__-
>register_method({
640 path
=> '{vmid}/rrd',
642 protected
=> 1, # fixme: can we avoid that?
644 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
646 description
=> "Read VM RRD statistics (returns PNG)",
648 additionalProperties
=> 0,
650 node
=> get_standard_option
('pve-node'),
651 vmid
=> get_standard_option
('pve-vmid'),
653 description
=> "Specify the time frame you are interested in.",
655 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
658 description
=> "The list of datasources you want to display.",
659 type
=> 'string', format
=> 'pve-configid-list',
662 description
=> "The RRD consolidation function",
664 enum
=> [ 'AVERAGE', 'MAX' ],
672 filename
=> { type
=> 'string' },
678 return PVE
::Cluster
::create_rrd_graph
(
679 "pve2-vm/$param->{vmid}", $param->{timeframe
},
680 $param->{ds
}, $param->{cf
});
684 __PACKAGE__-
>register_method({
686 path
=> '{vmid}/rrddata',
688 protected
=> 1, # fixme: can we avoid that?
690 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
692 description
=> "Read VM RRD statistics",
694 additionalProperties
=> 0,
696 node
=> get_standard_option
('pve-node'),
697 vmid
=> get_standard_option
('pve-vmid'),
699 description
=> "Specify the time frame you are interested in.",
701 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
704 description
=> "The RRD consolidation function",
706 enum
=> [ 'AVERAGE', 'MAX' ],
721 return PVE
::Cluster
::create_rrd_data
(
722 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
726 __PACKAGE__-
>register_method({
728 path
=> '{vmid}/config',
731 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
733 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
736 additionalProperties
=> 0,
738 node
=> get_standard_option
('pve-node'),
739 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
741 description
=> "Get current values (instead of pending values).",
753 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
760 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
762 delete $conf->{snapshots
};
764 if (!$param->{current
}) {
765 foreach my $opt (keys %{$conf->{pending
}}) {
766 next if $opt eq 'delete';
767 my $value = $conf->{pending
}->{$opt};
768 next if ref($value); # just to be sure
769 $conf->{$opt} = $value;
771 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
772 foreach my $opt (keys %$pending_delete_hash) {
773 delete $conf->{$opt} if $conf->{$opt};
777 delete $conf->{pending
};
782 __PACKAGE__-
>register_method({
783 name
=> 'vm_pending',
784 path
=> '{vmid}/pending',
787 description
=> "Get virtual machine configuration, including pending changes.",
789 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
792 additionalProperties
=> 0,
794 node
=> get_standard_option
('pve-node'),
795 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
804 description
=> "Configuration option name.",
808 description
=> "Current value.",
813 description
=> "Pending value.",
818 description
=> "Indicates a pending delete request if present and not 0. " .
819 "The value 2 indicates a force-delete request.",
831 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
833 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
837 foreach my $opt (keys %$conf) {
838 next if ref($conf->{$opt});
839 my $item = { key
=> $opt };
840 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
841 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
842 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
846 foreach my $opt (keys %{$conf->{pending
}}) {
847 next if $opt eq 'delete';
848 next if ref($conf->{pending
}->{$opt}); # just to be sure
849 next if defined($conf->{$opt});
850 my $item = { key
=> $opt };
851 $item->{pending
} = $conf->{pending
}->{$opt};
855 while (my ($opt, $force) = each %$pending_delete_hash) {
856 next if $conf->{pending
}->{$opt}; # just to be sure
857 next if $conf->{$opt};
858 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
865 # POST/PUT {vmid}/config implementation
867 # The original API used PUT (idempotent) an we assumed that all operations
868 # are fast. But it turned out that almost any configuration change can
869 # involve hot-plug actions, or disk alloc/free. Such actions can take long
870 # time to complete and have side effects (not idempotent).
872 # The new implementation uses POST and forks a worker process. We added
873 # a new option 'background_delay'. If specified we wait up to
874 # 'background_delay' second for the worker task to complete. It returns null
875 # if the task is finished within that time, else we return the UPID.
877 my $update_vm_api = sub {
878 my ($param, $sync) = @_;
880 my $rpcenv = PVE
::RPCEnvironment
::get
();
882 my $authuser = $rpcenv->get_user();
884 my $node = extract_param
($param, 'node');
886 my $vmid = extract_param
($param, 'vmid');
888 my $digest = extract_param
($param, 'digest');
890 my $background_delay = extract_param
($param, 'background_delay');
892 my @paramarr = (); # used for log message
893 foreach my $key (sort keys %$param) {
894 push @paramarr, "-$key", $param->{$key};
897 my $skiplock = extract_param
($param, 'skiplock');
898 raise_param_exc
({ skiplock
=> "Only root may use this option." })
899 if $skiplock && $authuser ne 'root@pam';
901 my $delete_str = extract_param
($param, 'delete');
903 my $revert_str = extract_param
($param, 'revert');
905 my $force = extract_param
($param, 'force');
907 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
909 my $storecfg = PVE
::Storage
::config
();
911 my $defaults = PVE
::QemuServer
::load_defaults
();
913 &$resolve_cdrom_alias($param);
915 # now try to verify all parameters
918 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
919 if (!PVE
::QemuServer
::option_exists
($opt)) {
920 raise_param_exc
({ revert
=> "unknown option '$opt'" });
923 raise_param_exc
({ delete => "you can't use '-$opt' and " .
924 "-revert $opt' at the same time" })
925 if defined($param->{$opt});
931 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
932 $opt = 'ide2' if $opt eq 'cdrom';
934 raise_param_exc
({ delete => "you can't use '-$opt' and " .
935 "-delete $opt' at the same time" })
936 if defined($param->{$opt});
938 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
939 "-revert $opt' at the same time" })
942 if (!PVE
::QemuServer
::option_exists
($opt)) {
943 raise_param_exc
({ delete => "unknown option '$opt'" });
949 my $repl_conf = PVE
::ReplicationConfig-
>new();
950 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
951 my $check_replication = sub {
953 return if !$is_replicated;
954 my $volid = $drive->{file
};
955 return if !$volid || !($drive->{replicate
}//1);
956 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
957 my ($storeid, $format);
958 if ($volid =~ $NEW_DISK_RE) {
960 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
962 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
963 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
965 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
966 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
967 return if $scfg->{shared
};
968 die "cannot add non-replicatable volume to a replicated VM\n";
971 foreach my $opt (keys %$param) {
972 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
974 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
975 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
976 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
977 $check_replication->($drive);
978 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
979 } elsif ($opt =~ m/^net(\d+)$/) {
981 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
982 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
986 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
988 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
990 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
994 my $conf = PVE
::QemuConfig-
>load_config($vmid);
996 die "checksum missmatch (file change by other user?)\n"
997 if $digest && $digest ne $conf->{digest
};
999 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1001 foreach my $opt (keys %$revert) {
1002 if (defined($conf->{$opt})) {
1003 $param->{$opt} = $conf->{$opt};
1004 } elsif (defined($conf->{pending
}->{$opt})) {
1009 if ($param->{memory
} || defined($param->{balloon
})) {
1010 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1011 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1013 die "balloon value too large (must be smaller than assigned memory)\n"
1014 if $balloon && $balloon > $maxmem;
1017 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1021 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1023 # write updates to pending section
1025 my $modified = {}; # record what $option we modify
1027 foreach my $opt (@delete) {
1028 $modified->{$opt} = 1;
1029 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1030 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1031 warn "cannot delete '$opt' - not set in current configuration!\n";
1032 $modified->{$opt} = 0;
1036 if ($opt =~ m/^unused/) {
1037 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1038 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1039 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1040 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1041 delete $conf->{$opt};
1042 PVE
::QemuConfig-
>write_config($vmid, $conf);
1044 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1045 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1046 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1047 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1048 if defined($conf->{pending
}->{$opt});
1049 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1050 PVE
::QemuConfig-
>write_config($vmid, $conf);
1052 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1053 PVE
::QemuConfig-
>write_config($vmid, $conf);
1057 foreach my $opt (keys %$param) { # add/change
1058 $modified->{$opt} = 1;
1059 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1060 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1062 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1063 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1064 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1065 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1067 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1069 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1070 if defined($conf->{pending
}->{$opt});
1072 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1074 $conf->{pending
}->{$opt} = $param->{$opt};
1076 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1077 PVE
::QemuConfig-
>write_config($vmid, $conf);
1080 # remove pending changes when nothing changed
1081 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1082 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1083 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1085 return if !scalar(keys %{$conf->{pending
}});
1087 my $running = PVE
::QemuServer
::check_running
($vmid);
1089 # apply pending changes
1091 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1095 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1096 raise_param_exc
($errors) if scalar(keys %$errors);
1098 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1108 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1110 if ($background_delay) {
1112 # Note: It would be better to do that in the Event based HTTPServer
1113 # to avoid blocking call to sleep.
1115 my $end_time = time() + $background_delay;
1117 my $task = PVE
::Tools
::upid_decode
($upid);
1120 while (time() < $end_time) {
1121 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1123 sleep(1); # this gets interrupted when child process ends
1127 my $status = PVE
::Tools
::upid_read_status
($upid);
1128 return undef if $status eq 'OK';
1137 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1140 my $vm_config_perm_list = [
1145 'VM.Config.Network',
1147 'VM.Config.Options',
1150 __PACKAGE__-
>register_method({
1151 name
=> 'update_vm_async',
1152 path
=> '{vmid}/config',
1156 description
=> "Set virtual machine options (asynchrounous API).",
1158 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1161 additionalProperties
=> 0,
1162 properties
=> PVE
::QemuServer
::json_config_properties
(
1164 node
=> get_standard_option
('pve-node'),
1165 vmid
=> get_standard_option
('pve-vmid'),
1166 skiplock
=> get_standard_option
('skiplock'),
1168 type
=> 'string', format
=> 'pve-configid-list',
1169 description
=> "A list of settings you want to delete.",
1173 type
=> 'string', format
=> 'pve-configid-list',
1174 description
=> "Revert a pending change.",
1179 description
=> $opt_force_description,
1181 requires
=> 'delete',
1185 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1189 background_delay
=> {
1191 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1202 code
=> $update_vm_api,
1205 __PACKAGE__-
>register_method({
1206 name
=> 'update_vm',
1207 path
=> '{vmid}/config',
1211 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1213 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1216 additionalProperties
=> 0,
1217 properties
=> PVE
::QemuServer
::json_config_properties
(
1219 node
=> get_standard_option
('pve-node'),
1220 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1221 skiplock
=> get_standard_option
('skiplock'),
1223 type
=> 'string', format
=> 'pve-configid-list',
1224 description
=> "A list of settings you want to delete.",
1228 type
=> 'string', format
=> 'pve-configid-list',
1229 description
=> "Revert a pending change.",
1234 description
=> $opt_force_description,
1236 requires
=> 'delete',
1240 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1246 returns
=> { type
=> 'null' },
1249 &$update_vm_api($param, 1);
1255 __PACKAGE__-
>register_method({
1256 name
=> 'destroy_vm',
1261 description
=> "Destroy the vm (also delete all used/owned volumes).",
1263 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1266 additionalProperties
=> 0,
1268 node
=> get_standard_option
('pve-node'),
1269 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1270 skiplock
=> get_standard_option
('skiplock'),
1279 my $rpcenv = PVE
::RPCEnvironment
::get
();
1281 my $authuser = $rpcenv->get_user();
1283 my $vmid = $param->{vmid
};
1285 my $skiplock = $param->{skiplock
};
1286 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1287 if $skiplock && $authuser ne 'root@pam';
1290 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1292 my $storecfg = PVE
::Storage
::config
();
1294 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1296 die "unable to remove VM $vmid - used in HA resources\n"
1297 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1299 # do not allow destroy if there are replication jobs
1300 my $repl_conf = PVE
::ReplicationConfig-
>new();
1301 $repl_conf->check_for_existing_jobs($vmid);
1303 # early tests (repeat after locking)
1304 die "VM $vmid is running - destroy failed\n"
1305 if PVE
::QemuServer
::check_running
($vmid);
1310 syslog
('info', "destroy VM $vmid: $upid\n");
1312 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1314 PVE
::AccessControl
::remove_vm_access
($vmid);
1316 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1319 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1322 __PACKAGE__-
>register_method({
1324 path
=> '{vmid}/unlink',
1328 description
=> "Unlink/delete disk images.",
1330 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1333 additionalProperties
=> 0,
1335 node
=> get_standard_option
('pve-node'),
1336 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1338 type
=> 'string', format
=> 'pve-configid-list',
1339 description
=> "A list of disk IDs you want to delete.",
1343 description
=> $opt_force_description,
1348 returns
=> { type
=> 'null'},
1352 $param->{delete} = extract_param
($param, 'idlist');
1354 __PACKAGE__-
>update_vm($param);
1361 __PACKAGE__-
>register_method({
1363 path
=> '{vmid}/vncproxy',
1367 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1369 description
=> "Creates a TCP VNC proxy connections.",
1371 additionalProperties
=> 0,
1373 node
=> get_standard_option
('pve-node'),
1374 vmid
=> get_standard_option
('pve-vmid'),
1378 description
=> "starts websockify instead of vncproxy",
1383 additionalProperties
=> 0,
1385 user
=> { type
=> 'string' },
1386 ticket
=> { type
=> 'string' },
1387 cert
=> { type
=> 'string' },
1388 port
=> { type
=> 'integer' },
1389 upid
=> { type
=> 'string' },
1395 my $rpcenv = PVE
::RPCEnvironment
::get
();
1397 my $authuser = $rpcenv->get_user();
1399 my $vmid = $param->{vmid
};
1400 my $node = $param->{node
};
1401 my $websocket = $param->{websocket
};
1403 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1405 my $authpath = "/vms/$vmid";
1407 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1409 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1412 my ($remip, $family);
1415 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1416 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1417 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1418 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1420 $family = PVE
::Tools
::get_host_address_family
($node);
1423 my $port = PVE
::Tools
::next_vnc_port
($family);
1430 syslog
('info', "starting vnc proxy $upid\n");
1434 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1436 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1438 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1439 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1440 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1441 '-timeout', $timeout, '-authpath', $authpath,
1442 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1443 PVE
::Tools
::run_command
($cmd);
1446 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1448 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1450 my $sock = IO
::Socket
::IP-
>new(
1455 GetAddrInfoFlags
=> 0,
1456 ) or die "failed to create socket: $!\n";
1457 # Inside the worker we shouldn't have any previous alarms
1458 # running anyway...:
1460 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1462 accept(my $cli, $sock) or die "connection failed: $!\n";
1465 if (PVE
::Tools
::run_command
($cmd,
1466 output
=> '>&'.fileno($cli),
1467 input
=> '<&'.fileno($cli),
1470 die "Failed to run vncproxy.\n";
1477 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1479 PVE
::Tools
::wait_for_vnc_port
($port);
1490 __PACKAGE__-
>register_method({
1491 name
=> 'vncwebsocket',
1492 path
=> '{vmid}/vncwebsocket',
1495 description
=> "You also need to pass a valid ticket (vncticket).",
1496 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1498 description
=> "Opens a weksocket for VNC traffic.",
1500 additionalProperties
=> 0,
1502 node
=> get_standard_option
('pve-node'),
1503 vmid
=> get_standard_option
('pve-vmid'),
1505 description
=> "Ticket from previous call to vncproxy.",
1510 description
=> "Port number returned by previous vncproxy call.",
1520 port
=> { type
=> 'string' },
1526 my $rpcenv = PVE
::RPCEnvironment
::get
();
1528 my $authuser = $rpcenv->get_user();
1530 my $vmid = $param->{vmid
};
1531 my $node = $param->{node
};
1533 my $authpath = "/vms/$vmid";
1535 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1537 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1539 # Note: VNC ports are acessible from outside, so we do not gain any
1540 # security if we verify that $param->{port} belongs to VM $vmid. This
1541 # check is done by verifying the VNC ticket (inside VNC protocol).
1543 my $port = $param->{port
};
1545 return { port
=> $port };
1548 __PACKAGE__-
>register_method({
1549 name
=> 'spiceproxy',
1550 path
=> '{vmid}/spiceproxy',
1555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1557 description
=> "Returns a SPICE configuration to connect to the VM.",
1559 additionalProperties
=> 0,
1561 node
=> get_standard_option
('pve-node'),
1562 vmid
=> get_standard_option
('pve-vmid'),
1563 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1566 returns
=> get_standard_option
('remote-viewer-config'),
1570 my $rpcenv = PVE
::RPCEnvironment
::get
();
1572 my $authuser = $rpcenv->get_user();
1574 my $vmid = $param->{vmid
};
1575 my $node = $param->{node
};
1576 my $proxy = $param->{proxy
};
1578 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1579 my $title = "VM $vmid";
1580 $title .= " - ". $conf->{name
} if $conf->{name
};
1582 my $port = PVE
::QemuServer
::spice_port
($vmid);
1584 my ($ticket, undef, $remote_viewer_config) =
1585 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1587 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1588 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1590 return $remote_viewer_config;
1593 __PACKAGE__-
>register_method({
1595 path
=> '{vmid}/status',
1598 description
=> "Directory index",
1603 additionalProperties
=> 0,
1605 node
=> get_standard_option
('pve-node'),
1606 vmid
=> get_standard_option
('pve-vmid'),
1614 subdir
=> { type
=> 'string' },
1617 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1623 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1626 { subdir
=> 'current' },
1627 { subdir
=> 'start' },
1628 { subdir
=> 'stop' },
1634 __PACKAGE__-
>register_method({
1635 name
=> 'vm_status',
1636 path
=> '{vmid}/status/current',
1639 protected
=> 1, # qemu pid files are only readable by root
1640 description
=> "Get virtual machine status.",
1642 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1645 additionalProperties
=> 0,
1647 node
=> get_standard_option
('pve-node'),
1648 vmid
=> get_standard_option
('pve-vmid'),
1651 returns
=> { type
=> 'object' },
1656 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1658 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1659 my $status = $vmstatus->{$param->{vmid
}};
1661 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1663 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1668 __PACKAGE__-
>register_method({
1670 path
=> '{vmid}/status/start',
1674 description
=> "Start virtual machine.",
1676 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1679 additionalProperties
=> 0,
1681 node
=> get_standard_option
('pve-node'),
1682 vmid
=> get_standard_option
('pve-vmid',
1683 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1684 skiplock
=> get_standard_option
('skiplock'),
1685 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1686 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1689 enum
=> ['secure', 'insecure'],
1690 description
=> "Migration traffic is encrypted using an SSH " .
1691 "tunnel by default. On secure, completely private networks " .
1692 "this can be disabled to increase performance.",
1695 migration_network
=> {
1696 type
=> 'string', format
=> 'CIDR',
1697 description
=> "CIDR of the (sub) network that is used for migration.",
1700 machine
=> get_standard_option
('pve-qm-machine'),
1702 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1714 my $rpcenv = PVE
::RPCEnvironment
::get
();
1716 my $authuser = $rpcenv->get_user();
1718 my $node = extract_param
($param, 'node');
1720 my $vmid = extract_param
($param, 'vmid');
1722 my $machine = extract_param
($param, 'machine');
1724 my $stateuri = extract_param
($param, 'stateuri');
1725 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1726 if $stateuri && $authuser ne 'root@pam';
1728 my $skiplock = extract_param
($param, 'skiplock');
1729 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1730 if $skiplock && $authuser ne 'root@pam';
1732 my $migratedfrom = extract_param
($param, 'migratedfrom');
1733 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1734 if $migratedfrom && $authuser ne 'root@pam';
1736 my $migration_type = extract_param
($param, 'migration_type');
1737 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1738 if $migration_type && $authuser ne 'root@pam';
1740 my $migration_network = extract_param
($param, 'migration_network');
1741 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1742 if $migration_network && $authuser ne 'root@pam';
1744 my $targetstorage = extract_param
($param, 'targetstorage');
1745 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1746 if $targetstorage && $authuser ne 'root@pam';
1748 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1749 if $targetstorage && !$migratedfrom;
1751 # read spice ticket from STDIN
1753 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1754 if (defined(my $line = <>)) {
1756 $spice_ticket = $line;
1760 PVE
::Cluster
::check_cfs_quorum
();
1762 my $storecfg = PVE
::Storage
::config
();
1764 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1765 $rpcenv->{type
} ne 'ha') {
1770 my $service = "vm:$vmid";
1772 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1774 print "Requesting HA start for VM $vmid\n";
1776 PVE
::Tools
::run_command
($cmd);
1781 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1788 syslog
('info', "start VM $vmid: $upid\n");
1790 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1791 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1796 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1800 __PACKAGE__-
>register_method({
1802 path
=> '{vmid}/status/stop',
1806 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1807 "is akin to pulling the power plug of a running computer and may damage the VM data",
1809 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1812 additionalProperties
=> 0,
1814 node
=> get_standard_option
('pve-node'),
1815 vmid
=> get_standard_option
('pve-vmid',
1816 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1817 skiplock
=> get_standard_option
('skiplock'),
1818 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1820 description
=> "Wait maximal timeout seconds.",
1826 description
=> "Do not deactivate storage volumes.",
1839 my $rpcenv = PVE
::RPCEnvironment
::get
();
1841 my $authuser = $rpcenv->get_user();
1843 my $node = extract_param
($param, 'node');
1845 my $vmid = extract_param
($param, 'vmid');
1847 my $skiplock = extract_param
($param, 'skiplock');
1848 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1849 if $skiplock && $authuser ne 'root@pam';
1851 my $keepActive = extract_param
($param, 'keepActive');
1852 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1853 if $keepActive && $authuser ne 'root@pam';
1855 my $migratedfrom = extract_param
($param, 'migratedfrom');
1856 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1857 if $migratedfrom && $authuser ne 'root@pam';
1860 my $storecfg = PVE
::Storage
::config
();
1862 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1867 my $service = "vm:$vmid";
1869 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1871 print "Requesting HA stop for VM $vmid\n";
1873 PVE
::Tools
::run_command
($cmd);
1878 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1884 syslog
('info', "stop VM $vmid: $upid\n");
1886 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1887 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1892 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1896 __PACKAGE__-
>register_method({
1898 path
=> '{vmid}/status/reset',
1902 description
=> "Reset virtual machine.",
1904 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1907 additionalProperties
=> 0,
1909 node
=> get_standard_option
('pve-node'),
1910 vmid
=> get_standard_option
('pve-vmid',
1911 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1912 skiplock
=> get_standard_option
('skiplock'),
1921 my $rpcenv = PVE
::RPCEnvironment
::get
();
1923 my $authuser = $rpcenv->get_user();
1925 my $node = extract_param
($param, 'node');
1927 my $vmid = extract_param
($param, 'vmid');
1929 my $skiplock = extract_param
($param, 'skiplock');
1930 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1931 if $skiplock && $authuser ne 'root@pam';
1933 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1938 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1943 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1946 __PACKAGE__-
>register_method({
1947 name
=> 'vm_shutdown',
1948 path
=> '{vmid}/status/shutdown',
1952 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1953 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
1955 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1958 additionalProperties
=> 0,
1960 node
=> get_standard_option
('pve-node'),
1961 vmid
=> get_standard_option
('pve-vmid',
1962 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1963 skiplock
=> get_standard_option
('skiplock'),
1965 description
=> "Wait maximal timeout seconds.",
1971 description
=> "Make sure the VM stops.",
1977 description
=> "Do not deactivate storage volumes.",
1990 my $rpcenv = PVE
::RPCEnvironment
::get
();
1992 my $authuser = $rpcenv->get_user();
1994 my $node = extract_param
($param, 'node');
1996 my $vmid = extract_param
($param, 'vmid');
1998 my $skiplock = extract_param
($param, 'skiplock');
1999 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2000 if $skiplock && $authuser ne 'root@pam';
2002 my $keepActive = extract_param
($param, 'keepActive');
2003 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2004 if $keepActive && $authuser ne 'root@pam';
2006 my $storecfg = PVE
::Storage
::config
();
2010 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2011 # otherwise, we will infer a shutdown command, but run into the timeout,
2012 # then when the vm is resumed, it will instantly shutdown
2014 # checking the qmp status here to get feedback to the gui/cli/api
2015 # and the status query should not take too long
2018 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2022 if (!$err && $qmpstatus->{status
} eq "paused") {
2023 if ($param->{forceStop
}) {
2024 warn "VM is paused - stop instead of shutdown\n";
2027 die "VM is paused - cannot shutdown\n";
2031 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2032 ($rpcenv->{type
} ne 'ha')) {
2037 my $service = "vm:$vmid";
2039 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2041 print "Requesting HA stop for VM $vmid\n";
2043 PVE
::Tools
::run_command
($cmd);
2048 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2055 syslog
('info', "shutdown VM $vmid: $upid\n");
2057 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2058 $shutdown, $param->{forceStop
}, $keepActive);
2063 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2067 __PACKAGE__-
>register_method({
2068 name
=> 'vm_suspend',
2069 path
=> '{vmid}/status/suspend',
2073 description
=> "Suspend virtual machine.",
2075 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2078 additionalProperties
=> 0,
2080 node
=> get_standard_option
('pve-node'),
2081 vmid
=> get_standard_option
('pve-vmid',
2082 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2083 skiplock
=> get_standard_option
('skiplock'),
2092 my $rpcenv = PVE
::RPCEnvironment
::get
();
2094 my $authuser = $rpcenv->get_user();
2096 my $node = extract_param
($param, 'node');
2098 my $vmid = extract_param
($param, 'vmid');
2100 my $skiplock = extract_param
($param, 'skiplock');
2101 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2102 if $skiplock && $authuser ne 'root@pam';
2104 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2109 syslog
('info', "suspend VM $vmid: $upid\n");
2111 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2116 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2119 __PACKAGE__-
>register_method({
2120 name
=> 'vm_resume',
2121 path
=> '{vmid}/status/resume',
2125 description
=> "Resume virtual machine.",
2127 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2130 additionalProperties
=> 0,
2132 node
=> get_standard_option
('pve-node'),
2133 vmid
=> get_standard_option
('pve-vmid',
2134 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2135 skiplock
=> get_standard_option
('skiplock'),
2136 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2146 my $rpcenv = PVE
::RPCEnvironment
::get
();
2148 my $authuser = $rpcenv->get_user();
2150 my $node = extract_param
($param, 'node');
2152 my $vmid = extract_param
($param, 'vmid');
2154 my $skiplock = extract_param
($param, 'skiplock');
2155 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2156 if $skiplock && $authuser ne 'root@pam';
2158 my $nocheck = extract_param
($param, 'nocheck');
2160 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2165 syslog
('info', "resume VM $vmid: $upid\n");
2167 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2172 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2175 __PACKAGE__-
>register_method({
2176 name
=> 'vm_sendkey',
2177 path
=> '{vmid}/sendkey',
2181 description
=> "Send key event to virtual machine.",
2183 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2186 additionalProperties
=> 0,
2188 node
=> get_standard_option
('pve-node'),
2189 vmid
=> get_standard_option
('pve-vmid',
2190 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2191 skiplock
=> get_standard_option
('skiplock'),
2193 description
=> "The key (qemu monitor encoding).",
2198 returns
=> { type
=> 'null'},
2202 my $rpcenv = PVE
::RPCEnvironment
::get
();
2204 my $authuser = $rpcenv->get_user();
2206 my $node = extract_param
($param, 'node');
2208 my $vmid = extract_param
($param, 'vmid');
2210 my $skiplock = extract_param
($param, 'skiplock');
2211 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2212 if $skiplock && $authuser ne 'root@pam';
2214 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2219 __PACKAGE__-
>register_method({
2220 name
=> 'vm_feature',
2221 path
=> '{vmid}/feature',
2225 description
=> "Check if feature for virtual machine is available.",
2227 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2230 additionalProperties
=> 0,
2232 node
=> get_standard_option
('pve-node'),
2233 vmid
=> get_standard_option
('pve-vmid'),
2235 description
=> "Feature to check.",
2237 enum
=> [ 'snapshot', 'clone', 'copy' ],
2239 snapname
=> get_standard_option
('pve-snapshot-name', {
2247 hasFeature
=> { type
=> 'boolean' },
2250 items
=> { type
=> 'string' },
2257 my $node = extract_param
($param, 'node');
2259 my $vmid = extract_param
($param, 'vmid');
2261 my $snapname = extract_param
($param, 'snapname');
2263 my $feature = extract_param
($param, 'feature');
2265 my $running = PVE
::QemuServer
::check_running
($vmid);
2267 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2270 my $snap = $conf->{snapshots
}->{$snapname};
2271 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2274 my $storecfg = PVE
::Storage
::config
();
2276 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2277 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2280 hasFeature
=> $hasFeature,
2281 nodes
=> [ keys %$nodelist ],
2285 __PACKAGE__-
>register_method({
2287 path
=> '{vmid}/clone',
2291 description
=> "Create a copy of virtual machine/template.",
2293 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2294 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2295 "'Datastore.AllocateSpace' on any used storage.",
2298 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2300 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2301 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2306 additionalProperties
=> 0,
2308 node
=> get_standard_option
('pve-node'),
2309 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2310 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2313 type
=> 'string', format
=> 'dns-name',
2314 description
=> "Set a name for the new VM.",
2319 description
=> "Description for the new VM.",
2323 type
=> 'string', format
=> 'pve-poolid',
2324 description
=> "Add the new VM to the specified pool.",
2326 snapname
=> get_standard_option
('pve-snapshot-name', {
2329 storage
=> get_standard_option
('pve-storage-id', {
2330 description
=> "Target storage for full clone.",
2335 description
=> "Target format for file storage.",
2339 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2344 description
=> "Create a full copy of all disk. This is always done when " .
2345 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2348 target
=> get_standard_option
('pve-node', {
2349 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2360 my $rpcenv = PVE
::RPCEnvironment
::get
();
2362 my $authuser = $rpcenv->get_user();
2364 my $node = extract_param
($param, 'node');
2366 my $vmid = extract_param
($param, 'vmid');
2368 my $newid = extract_param
($param, 'newid');
2370 my $pool = extract_param
($param, 'pool');
2372 if (defined($pool)) {
2373 $rpcenv->check_pool_exist($pool);
2376 my $snapname = extract_param
($param, 'snapname');
2378 my $storage = extract_param
($param, 'storage');
2380 my $format = extract_param
($param, 'format');
2382 my $target = extract_param
($param, 'target');
2384 my $localnode = PVE
::INotify
::nodename
();
2386 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2388 PVE
::Cluster
::check_node_exists
($target) if $target;
2390 my $storecfg = PVE
::Storage
::config
();
2393 # check if storage is enabled on local node
2394 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2396 # check if storage is available on target node
2397 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2398 # clone only works if target storage is shared
2399 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2400 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2404 PVE
::Cluster
::check_cfs_quorum
();
2406 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2408 # exclusive lock if VM is running - else shared lock is enough;
2409 my $shared_lock = $running ?
0 : 1;
2413 # do all tests after lock
2414 # we also try to do all tests before we fork the worker
2416 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2418 PVE
::QemuConfig-
>check_lock($conf);
2420 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2422 die "unexpected state change\n" if $verify_running != $running;
2424 die "snapshot '$snapname' does not exist\n"
2425 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2427 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2429 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2431 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2433 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2435 die "unable to create VM $newid: config file already exists\n"
2438 my $newconf = { lock => 'clone' };
2443 foreach my $opt (keys %$oldconf) {
2444 my $value = $oldconf->{$opt};
2446 # do not copy snapshot related info
2447 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2448 $opt eq 'vmstate' || $opt eq 'snapstate';
2450 # no need to copy unused images, because VMID(owner) changes anyways
2451 next if $opt =~ m/^unused\d+$/;
2453 # always change MAC! address
2454 if ($opt =~ m/^net(\d+)$/) {
2455 my $net = PVE
::QemuServer
::parse_net
($value);
2456 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2457 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2458 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2459 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2460 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2461 die "unable to parse drive options for '$opt'\n" if !$drive;
2462 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2463 $newconf->{$opt} = $value; # simply copy configuration
2465 if ($param->{full
}) {
2466 die "Full clone feature is not supported for drive '$opt'\n"
2467 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2468 $fullclone->{$opt} = 1;
2470 # not full means clone instead of copy
2471 die "Linked clone feature is not supported for drive '$opt'\n"
2472 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2474 $drives->{$opt} = $drive;
2475 push @$vollist, $drive->{file
};
2478 # copy everything else
2479 $newconf->{$opt} = $value;
2483 # auto generate a new uuid
2484 my ($uuid, $uuid_str);
2485 UUID
::generate
($uuid);
2486 UUID
::unparse
($uuid, $uuid_str);
2487 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2488 $smbios1->{uuid
} = $uuid_str;
2489 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2491 delete $newconf->{template
};
2493 if ($param->{name
}) {
2494 $newconf->{name
} = $param->{name
};
2496 if ($oldconf->{name
}) {
2497 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2499 $newconf->{name
} = "Copy-of-VM-$vmid";
2503 if ($param->{description
}) {
2504 $newconf->{description
} = $param->{description
};
2507 # create empty/temp config - this fails if VM already exists on other node
2508 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2513 my $newvollist = [];
2520 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2522 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2524 my $total_jobs = scalar(keys %{$drives});
2527 foreach my $opt (keys %$drives) {
2528 my $drive = $drives->{$opt};
2529 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2531 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2532 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2533 $jobs, $skipcomplete, $oldconf->{agent
});
2535 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2537 PVE
::QemuConfig-
>write_config($newid, $newconf);
2541 delete $newconf->{lock};
2542 PVE
::QemuConfig-
>write_config($newid, $newconf);
2545 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2546 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2547 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2549 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2550 die "Failed to move config to node '$target' - rename failed: $!\n"
2551 if !rename($conffile, $newconffile);
2554 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2559 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2561 sleep 1; # some storage like rbd need to wait before release volume - really?
2563 foreach my $volid (@$newvollist) {
2564 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2567 die "clone failed: $err";
2573 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2575 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2578 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2579 # Aquire exclusive lock lock for $newid
2580 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2585 __PACKAGE__-
>register_method({
2586 name
=> 'move_vm_disk',
2587 path
=> '{vmid}/move_disk',
2591 description
=> "Move volume to different storage.",
2593 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2595 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2596 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2600 additionalProperties
=> 0,
2602 node
=> get_standard_option
('pve-node'),
2603 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2606 description
=> "The disk you want to move.",
2607 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2609 storage
=> get_standard_option
('pve-storage-id', {
2610 description
=> "Target storage.",
2611 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2615 description
=> "Target Format.",
2616 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2621 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2627 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2635 description
=> "the task ID.",
2640 my $rpcenv = PVE
::RPCEnvironment
::get
();
2642 my $authuser = $rpcenv->get_user();
2644 my $node = extract_param
($param, 'node');
2646 my $vmid = extract_param
($param, 'vmid');
2648 my $digest = extract_param
($param, 'digest');
2650 my $disk = extract_param
($param, 'disk');
2652 my $storeid = extract_param
($param, 'storage');
2654 my $format = extract_param
($param, 'format');
2656 my $storecfg = PVE
::Storage
::config
();
2658 my $updatefn = sub {
2660 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2662 PVE
::QemuConfig-
>check_lock($conf);
2664 die "checksum missmatch (file change by other user?)\n"
2665 if $digest && $digest ne $conf->{digest
};
2667 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2669 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2671 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2673 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2676 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2677 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2681 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2682 (!$format || !$oldfmt || $oldfmt eq $format);
2684 # this only checks snapshots because $disk is passed!
2685 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2686 die "you can't move a disk with snapshots and delete the source\n"
2687 if $snapshotted && $param->{delete};
2689 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2691 my $running = PVE
::QemuServer
::check_running
($vmid);
2693 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2697 my $newvollist = [];
2703 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2705 warn "moving disk with snapshots, snapshots will not be moved!\n"
2708 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2709 $vmid, $storeid, $format, 1, $newvollist);
2711 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2713 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2715 # convert moved disk to base if part of template
2716 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2717 if PVE
::QemuConfig-
>is_template($conf);
2719 PVE
::QemuConfig-
>write_config($vmid, $conf);
2722 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2723 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2730 foreach my $volid (@$newvollist) {
2731 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2734 die "storage migration failed: $err";
2737 if ($param->{delete}) {
2739 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2740 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2746 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2749 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2752 __PACKAGE__-
>register_method({
2753 name
=> 'migrate_vm',
2754 path
=> '{vmid}/migrate',
2758 description
=> "Migrate virtual machine. Creates a new migration task.",
2760 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2763 additionalProperties
=> 0,
2765 node
=> get_standard_option
('pve-node'),
2766 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2767 target
=> get_standard_option
('pve-node', {
2768 description
=> "Target node.",
2769 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2773 description
=> "Use online/live migration.",
2778 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2783 enum
=> ['secure', 'insecure'],
2784 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2787 migration_network
=> {
2788 type
=> 'string', format
=> 'CIDR',
2789 description
=> "CIDR of the (sub) network that is used for migration.",
2792 "with-local-disks" => {
2794 description
=> "Enable live storage migration for local disk",
2797 targetstorage
=> get_standard_option
('pve-storage-id', {
2798 description
=> "Default target storage.",
2800 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2806 description
=> "the task ID.",
2811 my $rpcenv = PVE
::RPCEnvironment
::get
();
2813 my $authuser = $rpcenv->get_user();
2815 my $target = extract_param
($param, 'target');
2817 my $localnode = PVE
::INotify
::nodename
();
2818 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2820 PVE
::Cluster
::check_cfs_quorum
();
2822 PVE
::Cluster
::check_node_exists
($target);
2824 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2826 my $vmid = extract_param
($param, 'vmid');
2828 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2829 if !$param->{online
} && $param->{targetstorage
};
2831 raise_param_exc
({ force
=> "Only root may use this option." })
2832 if $param->{force
} && $authuser ne 'root@pam';
2834 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2835 if $param->{migration_type
} && $authuser ne 'root@pam';
2837 # allow root only until better network permissions are available
2838 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2839 if $param->{migration_network
} && $authuser ne 'root@pam';
2842 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2844 # try to detect errors early
2846 PVE
::QemuConfig-
>check_lock($conf);
2848 if (PVE
::QemuServer
::check_running
($vmid)) {
2849 die "cant migrate running VM without --online\n"
2850 if !$param->{online
};
2853 my $storecfg = PVE
::Storage
::config
();
2855 if( $param->{targetstorage
}) {
2856 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2858 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2861 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2866 my $service = "vm:$vmid";
2868 my $cmd = ['ha-manager', 'migrate', $service, $target];
2870 print "Requesting HA migration for VM $vmid to node $target\n";
2872 PVE
::Tools
::run_command
($cmd);
2877 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2882 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2886 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2889 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2894 __PACKAGE__-
>register_method({
2896 path
=> '{vmid}/monitor',
2900 description
=> "Execute Qemu monitor commands.",
2902 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2906 additionalProperties
=> 0,
2908 node
=> get_standard_option
('pve-node'),
2909 vmid
=> get_standard_option
('pve-vmid'),
2912 description
=> "The monitor command.",
2916 returns
=> { type
=> 'string'},
2920 my $rpcenv = PVE
::RPCEnvironment
::get
();
2921 my $authuser = $rpcenv->get_user();
2924 my $command = shift;
2925 return $command =~ m/^\s*info(\s+|$)/
2926 || $command =~ m/^\s*help\s*$/;
2929 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2930 if !&$is_ro($param->{command
});
2932 my $vmid = $param->{vmid
};
2934 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2938 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2940 $res = "ERROR: $@" if $@;
2945 my $guest_agent_commands = [
2953 'network-get-interfaces',
2956 'get-memory-blocks',
2957 'get-memory-block-info',
2964 __PACKAGE__-
>register_method({
2966 path
=> '{vmid}/agent',
2970 description
=> "Execute Qemu Guest Agent commands.",
2972 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2975 additionalProperties
=> 0,
2977 node
=> get_standard_option
('pve-node'),
2978 vmid
=> get_standard_option
('pve-vmid', {
2979 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2982 description
=> "The QGA command.",
2983 enum
=> $guest_agent_commands,
2989 description
=> "Returns an object with a single `result` property. The type of that
2990 property depends on the executed command.",
2995 my $vmid = $param->{vmid
};
2997 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2999 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3000 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3002 my $cmd = $param->{command
};
3004 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3006 return { result
=> $res };
3009 __PACKAGE__-
>register_method({
3010 name
=> 'resize_vm',
3011 path
=> '{vmid}/resize',
3015 description
=> "Extend volume size.",
3017 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3020 additionalProperties
=> 0,
3022 node
=> get_standard_option
('pve-node'),
3023 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3024 skiplock
=> get_standard_option
('skiplock'),
3027 description
=> "The disk you want to resize.",
3028 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3032 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3033 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.",
3037 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3043 returns
=> { type
=> 'null'},
3047 my $rpcenv = PVE
::RPCEnvironment
::get
();
3049 my $authuser = $rpcenv->get_user();
3051 my $node = extract_param
($param, 'node');
3053 my $vmid = extract_param
($param, 'vmid');
3055 my $digest = extract_param
($param, 'digest');
3057 my $disk = extract_param
($param, 'disk');
3059 my $sizestr = extract_param
($param, 'size');
3061 my $skiplock = extract_param
($param, 'skiplock');
3062 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3063 if $skiplock && $authuser ne 'root@pam';
3065 my $storecfg = PVE
::Storage
::config
();
3067 my $updatefn = sub {
3069 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3071 die "checksum missmatch (file change by other user?)\n"
3072 if $digest && $digest ne $conf->{digest
};
3073 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3075 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3077 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3079 my (undef, undef, undef, undef, undef, undef, $format) =
3080 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3082 die "can't resize volume: $disk if snapshot exists\n"
3083 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3085 my $volid = $drive->{file
};
3087 die "disk '$disk' has no associated volume\n" if !$volid;
3089 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3091 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3093 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3095 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3096 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3098 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3099 my ($ext, $newsize, $unit) = ($1, $2, $4);
3102 $newsize = $newsize * 1024;
3103 } elsif ($unit eq 'M') {
3104 $newsize = $newsize * 1024 * 1024;
3105 } elsif ($unit eq 'G') {
3106 $newsize = $newsize * 1024 * 1024 * 1024;
3107 } elsif ($unit eq 'T') {
3108 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3111 $newsize += $size if $ext;
3112 $newsize = int($newsize);
3114 die "shrinking disks is not supported\n" if $newsize < $size;
3116 return if $size == $newsize;
3118 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3120 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3122 $drive->{size
} = $newsize;
3123 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3125 PVE
::QemuConfig-
>write_config($vmid, $conf);
3128 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3132 __PACKAGE__-
>register_method({
3133 name
=> 'snapshot_list',
3134 path
=> '{vmid}/snapshot',
3136 description
=> "List all snapshots.",
3138 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3141 protected
=> 1, # qemu pid files are only readable by root
3143 additionalProperties
=> 0,
3145 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3146 node
=> get_standard_option
('pve-node'),
3155 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3160 my $vmid = $param->{vmid
};
3162 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3163 my $snaphash = $conf->{snapshots
} || {};
3167 foreach my $name (keys %$snaphash) {
3168 my $d = $snaphash->{$name};
3171 snaptime
=> $d->{snaptime
} || 0,
3172 vmstate
=> $d->{vmstate
} ?
1 : 0,
3173 description
=> $d->{description
} || '',
3175 $item->{parent
} = $d->{parent
} if $d->{parent
};
3176 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3180 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3181 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3182 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3184 push @$res, $current;
3189 __PACKAGE__-
>register_method({
3191 path
=> '{vmid}/snapshot',
3195 description
=> "Snapshot a VM.",
3197 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3200 additionalProperties
=> 0,
3202 node
=> get_standard_option
('pve-node'),
3203 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3204 snapname
=> get_standard_option
('pve-snapshot-name'),
3208 description
=> "Save the vmstate",
3213 description
=> "A textual description or comment.",
3219 description
=> "the task ID.",
3224 my $rpcenv = PVE
::RPCEnvironment
::get
();
3226 my $authuser = $rpcenv->get_user();
3228 my $node = extract_param
($param, 'node');
3230 my $vmid = extract_param
($param, 'vmid');
3232 my $snapname = extract_param
($param, 'snapname');
3234 die "unable to use snapshot name 'current' (reserved name)\n"
3235 if $snapname eq 'current';
3238 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3239 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3240 $param->{description
});
3243 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3246 __PACKAGE__-
>register_method({
3247 name
=> 'snapshot_cmd_idx',
3248 path
=> '{vmid}/snapshot/{snapname}',
3255 additionalProperties
=> 0,
3257 vmid
=> get_standard_option
('pve-vmid'),
3258 node
=> get_standard_option
('pve-node'),
3259 snapname
=> get_standard_option
('pve-snapshot-name'),
3268 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3275 push @$res, { cmd
=> 'rollback' };
3276 push @$res, { cmd
=> 'config' };
3281 __PACKAGE__-
>register_method({
3282 name
=> 'update_snapshot_config',
3283 path
=> '{vmid}/snapshot/{snapname}/config',
3287 description
=> "Update snapshot metadata.",
3289 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3292 additionalProperties
=> 0,
3294 node
=> get_standard_option
('pve-node'),
3295 vmid
=> get_standard_option
('pve-vmid'),
3296 snapname
=> get_standard_option
('pve-snapshot-name'),
3300 description
=> "A textual description or comment.",
3304 returns
=> { type
=> 'null' },
3308 my $rpcenv = PVE
::RPCEnvironment
::get
();
3310 my $authuser = $rpcenv->get_user();
3312 my $vmid = extract_param
($param, 'vmid');
3314 my $snapname = extract_param
($param, 'snapname');
3316 return undef if !defined($param->{description
});
3318 my $updatefn = sub {
3320 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3322 PVE
::QemuConfig-
>check_lock($conf);
3324 my $snap = $conf->{snapshots
}->{$snapname};
3326 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3328 $snap->{description
} = $param->{description
} if defined($param->{description
});
3330 PVE
::QemuConfig-
>write_config($vmid, $conf);
3333 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3338 __PACKAGE__-
>register_method({
3339 name
=> 'get_snapshot_config',
3340 path
=> '{vmid}/snapshot/{snapname}/config',
3343 description
=> "Get snapshot configuration",
3345 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3348 additionalProperties
=> 0,
3350 node
=> get_standard_option
('pve-node'),
3351 vmid
=> get_standard_option
('pve-vmid'),
3352 snapname
=> get_standard_option
('pve-snapshot-name'),
3355 returns
=> { type
=> "object" },
3359 my $rpcenv = PVE
::RPCEnvironment
::get
();
3361 my $authuser = $rpcenv->get_user();
3363 my $vmid = extract_param
($param, 'vmid');
3365 my $snapname = extract_param
($param, 'snapname');
3367 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3369 my $snap = $conf->{snapshots
}->{$snapname};
3371 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3376 __PACKAGE__-
>register_method({
3378 path
=> '{vmid}/snapshot/{snapname}/rollback',
3382 description
=> "Rollback VM state to specified snapshot.",
3384 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3387 additionalProperties
=> 0,
3389 node
=> get_standard_option
('pve-node'),
3390 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3391 snapname
=> get_standard_option
('pve-snapshot-name'),
3396 description
=> "the task ID.",
3401 my $rpcenv = PVE
::RPCEnvironment
::get
();
3403 my $authuser = $rpcenv->get_user();
3405 my $node = extract_param
($param, 'node');
3407 my $vmid = extract_param
($param, 'vmid');
3409 my $snapname = extract_param
($param, 'snapname');
3412 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3413 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3417 # hold migration lock, this makes sure that nobody create replication snapshots
3418 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3421 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3424 __PACKAGE__-
>register_method({
3425 name
=> 'delsnapshot',
3426 path
=> '{vmid}/snapshot/{snapname}',
3430 description
=> "Delete a VM snapshot.",
3432 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3435 additionalProperties
=> 0,
3437 node
=> get_standard_option
('pve-node'),
3438 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3439 snapname
=> get_standard_option
('pve-snapshot-name'),
3443 description
=> "For removal from config file, even if removing disk snapshots fails.",
3449 description
=> "the task ID.",
3454 my $rpcenv = PVE
::RPCEnvironment
::get
();
3456 my $authuser = $rpcenv->get_user();
3458 my $node = extract_param
($param, 'node');
3460 my $vmid = extract_param
($param, 'vmid');
3462 my $snapname = extract_param
($param, 'snapname');
3465 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3466 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3469 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3472 __PACKAGE__-
>register_method({
3474 path
=> '{vmid}/template',
3478 description
=> "Create a Template.",
3480 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3481 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3484 additionalProperties
=> 0,
3486 node
=> get_standard_option
('pve-node'),
3487 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3491 description
=> "If you want to convert only 1 disk to base image.",
3492 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3497 returns
=> { type
=> 'null'},
3501 my $rpcenv = PVE
::RPCEnvironment
::get
();
3503 my $authuser = $rpcenv->get_user();
3505 my $node = extract_param
($param, 'node');
3507 my $vmid = extract_param
($param, 'vmid');
3509 my $disk = extract_param
($param, 'disk');
3511 my $updatefn = sub {
3513 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3515 PVE
::QemuConfig-
>check_lock($conf);
3517 die "unable to create template, because VM contains snapshots\n"
3518 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3520 die "you can't convert a template to a template\n"
3521 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3523 die "you can't convert a VM to template if VM is running\n"
3524 if PVE
::QemuServer
::check_running
($vmid);
3527 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3530 $conf->{template
} = 1;
3531 PVE
::QemuConfig-
>write_config($vmid, $conf);
3533 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3536 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);