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 = [];
2700 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2702 warn "moving disk with snapshots, snapshots will not be moved!\n"
2705 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2706 $vmid, $storeid, $format, 1, $newvollist);
2708 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2710 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2712 # convert moved disk to base if part of template
2713 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2714 if PVE
::QemuConfig-
>is_template($conf);
2716 PVE
::QemuConfig-
>write_config($vmid, $conf);
2719 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2720 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2727 foreach my $volid (@$newvollist) {
2728 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2731 die "storage migration failed: $err";
2734 if ($param->{delete}) {
2736 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2737 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2743 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2746 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2749 __PACKAGE__-
>register_method({
2750 name
=> 'migrate_vm',
2751 path
=> '{vmid}/migrate',
2755 description
=> "Migrate virtual machine. Creates a new migration task.",
2757 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2760 additionalProperties
=> 0,
2762 node
=> get_standard_option
('pve-node'),
2763 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2764 target
=> get_standard_option
('pve-node', {
2765 description
=> "Target node.",
2766 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2770 description
=> "Use online/live migration.",
2775 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2780 enum
=> ['secure', 'insecure'],
2781 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2784 migration_network
=> {
2785 type
=> 'string', format
=> 'CIDR',
2786 description
=> "CIDR of the (sub) network that is used for migration.",
2789 "with-local-disks" => {
2791 description
=> "Enable live storage migration for local disk",
2794 targetstorage
=> get_standard_option
('pve-storage-id', {
2795 description
=> "Default target storage.",
2797 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2803 description
=> "the task ID.",
2808 my $rpcenv = PVE
::RPCEnvironment
::get
();
2810 my $authuser = $rpcenv->get_user();
2812 my $target = extract_param
($param, 'target');
2814 my $localnode = PVE
::INotify
::nodename
();
2815 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2817 PVE
::Cluster
::check_cfs_quorum
();
2819 PVE
::Cluster
::check_node_exists
($target);
2821 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2823 my $vmid = extract_param
($param, 'vmid');
2825 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2826 if !$param->{online
} && $param->{targetstorage
};
2828 raise_param_exc
({ force
=> "Only root may use this option." })
2829 if $param->{force
} && $authuser ne 'root@pam';
2831 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2832 if $param->{migration_type
} && $authuser ne 'root@pam';
2834 # allow root only until better network permissions are available
2835 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2836 if $param->{migration_network
} && $authuser ne 'root@pam';
2839 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2841 # try to detect errors early
2843 PVE
::QemuConfig-
>check_lock($conf);
2845 if (PVE
::QemuServer
::check_running
($vmid)) {
2846 die "cant migrate running VM without --online\n"
2847 if !$param->{online
};
2850 my $storecfg = PVE
::Storage
::config
();
2852 if( $param->{targetstorage
}) {
2853 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2855 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2858 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2863 my $service = "vm:$vmid";
2865 my $cmd = ['ha-manager', 'migrate', $service, $target];
2867 print "Requesting HA migration for VM $vmid to node $target\n";
2869 PVE
::Tools
::run_command
($cmd);
2874 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2879 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2883 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2886 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2891 __PACKAGE__-
>register_method({
2893 path
=> '{vmid}/monitor',
2897 description
=> "Execute Qemu monitor commands.",
2899 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2900 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2903 additionalProperties
=> 0,
2905 node
=> get_standard_option
('pve-node'),
2906 vmid
=> get_standard_option
('pve-vmid'),
2909 description
=> "The monitor command.",
2913 returns
=> { type
=> 'string'},
2917 my $rpcenv = PVE
::RPCEnvironment
::get
();
2918 my $authuser = $rpcenv->get_user();
2921 my $command = shift;
2922 return $command =~ m/^\s*info(\s+|$)/
2923 || $command =~ m/^\s*help\s*$/;
2926 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2927 if !&$is_ro($param->{command
});
2929 my $vmid = $param->{vmid
};
2931 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2935 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2937 $res = "ERROR: $@" if $@;
2942 my $guest_agent_commands = [
2950 'network-get-interfaces',
2953 'get-memory-blocks',
2954 'get-memory-block-info',
2961 __PACKAGE__-
>register_method({
2963 path
=> '{vmid}/agent',
2967 description
=> "Execute Qemu Guest Agent commands.",
2969 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2972 additionalProperties
=> 0,
2974 node
=> get_standard_option
('pve-node'),
2975 vmid
=> get_standard_option
('pve-vmid', {
2976 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2979 description
=> "The QGA command.",
2980 enum
=> $guest_agent_commands,
2986 description
=> "Returns an object with a single `result` property. The type of that
2987 property depends on the executed command.",
2992 my $vmid = $param->{vmid
};
2994 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
2996 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
2997 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2999 my $cmd = $param->{command
};
3001 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3003 return { result
=> $res };
3006 __PACKAGE__-
>register_method({
3007 name
=> 'resize_vm',
3008 path
=> '{vmid}/resize',
3012 description
=> "Extend volume size.",
3014 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3017 additionalProperties
=> 0,
3019 node
=> get_standard_option
('pve-node'),
3020 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3021 skiplock
=> get_standard_option
('skiplock'),
3024 description
=> "The disk you want to resize.",
3025 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3029 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3030 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.",
3034 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3040 returns
=> { type
=> 'null'},
3044 my $rpcenv = PVE
::RPCEnvironment
::get
();
3046 my $authuser = $rpcenv->get_user();
3048 my $node = extract_param
($param, 'node');
3050 my $vmid = extract_param
($param, 'vmid');
3052 my $digest = extract_param
($param, 'digest');
3054 my $disk = extract_param
($param, 'disk');
3056 my $sizestr = extract_param
($param, 'size');
3058 my $skiplock = extract_param
($param, 'skiplock');
3059 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3060 if $skiplock && $authuser ne 'root@pam';
3062 my $storecfg = PVE
::Storage
::config
();
3064 my $updatefn = sub {
3066 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3068 die "checksum missmatch (file change by other user?)\n"
3069 if $digest && $digest ne $conf->{digest
};
3070 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3072 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3074 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3076 my (undef, undef, undef, undef, undef, undef, $format) =
3077 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3079 die "can't resize volume: $disk if snapshot exists\n"
3080 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3082 my $volid = $drive->{file
};
3084 die "disk '$disk' has no associated volume\n" if !$volid;
3086 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3088 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3090 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3092 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3093 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3095 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3096 my ($ext, $newsize, $unit) = ($1, $2, $4);
3099 $newsize = $newsize * 1024;
3100 } elsif ($unit eq 'M') {
3101 $newsize = $newsize * 1024 * 1024;
3102 } elsif ($unit eq 'G') {
3103 $newsize = $newsize * 1024 * 1024 * 1024;
3104 } elsif ($unit eq 'T') {
3105 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3108 $newsize += $size if $ext;
3109 $newsize = int($newsize);
3111 die "shrinking disks is not supported\n" if $newsize < $size;
3113 return if $size == $newsize;
3115 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3117 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3119 $drive->{size
} = $newsize;
3120 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3122 PVE
::QemuConfig-
>write_config($vmid, $conf);
3125 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3129 __PACKAGE__-
>register_method({
3130 name
=> 'snapshot_list',
3131 path
=> '{vmid}/snapshot',
3133 description
=> "List all snapshots.",
3135 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3138 protected
=> 1, # qemu pid files are only readable by root
3140 additionalProperties
=> 0,
3142 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3143 node
=> get_standard_option
('pve-node'),
3152 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3157 my $vmid = $param->{vmid
};
3159 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3160 my $snaphash = $conf->{snapshots
} || {};
3164 foreach my $name (keys %$snaphash) {
3165 my $d = $snaphash->{$name};
3168 snaptime
=> $d->{snaptime
} || 0,
3169 vmstate
=> $d->{vmstate
} ?
1 : 0,
3170 description
=> $d->{description
} || '',
3172 $item->{parent
} = $d->{parent
} if $d->{parent
};
3173 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3177 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3178 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3179 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3181 push @$res, $current;
3186 __PACKAGE__-
>register_method({
3188 path
=> '{vmid}/snapshot',
3192 description
=> "Snapshot a VM.",
3194 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3197 additionalProperties
=> 0,
3199 node
=> get_standard_option
('pve-node'),
3200 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3201 snapname
=> get_standard_option
('pve-snapshot-name'),
3205 description
=> "Save the vmstate",
3210 description
=> "A textual description or comment.",
3216 description
=> "the task ID.",
3221 my $rpcenv = PVE
::RPCEnvironment
::get
();
3223 my $authuser = $rpcenv->get_user();
3225 my $node = extract_param
($param, 'node');
3227 my $vmid = extract_param
($param, 'vmid');
3229 my $snapname = extract_param
($param, 'snapname');
3231 die "unable to use snapshot name 'current' (reserved name)\n"
3232 if $snapname eq 'current';
3235 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3236 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3237 $param->{description
});
3240 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3243 __PACKAGE__-
>register_method({
3244 name
=> 'snapshot_cmd_idx',
3245 path
=> '{vmid}/snapshot/{snapname}',
3252 additionalProperties
=> 0,
3254 vmid
=> get_standard_option
('pve-vmid'),
3255 node
=> get_standard_option
('pve-node'),
3256 snapname
=> get_standard_option
('pve-snapshot-name'),
3265 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3272 push @$res, { cmd
=> 'rollback' };
3273 push @$res, { cmd
=> 'config' };
3278 __PACKAGE__-
>register_method({
3279 name
=> 'update_snapshot_config',
3280 path
=> '{vmid}/snapshot/{snapname}/config',
3284 description
=> "Update snapshot metadata.",
3286 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3289 additionalProperties
=> 0,
3291 node
=> get_standard_option
('pve-node'),
3292 vmid
=> get_standard_option
('pve-vmid'),
3293 snapname
=> get_standard_option
('pve-snapshot-name'),
3297 description
=> "A textual description or comment.",
3301 returns
=> { type
=> 'null' },
3305 my $rpcenv = PVE
::RPCEnvironment
::get
();
3307 my $authuser = $rpcenv->get_user();
3309 my $vmid = extract_param
($param, 'vmid');
3311 my $snapname = extract_param
($param, 'snapname');
3313 return undef if !defined($param->{description
});
3315 my $updatefn = sub {
3317 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3319 PVE
::QemuConfig-
>check_lock($conf);
3321 my $snap = $conf->{snapshots
}->{$snapname};
3323 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3325 $snap->{description
} = $param->{description
} if defined($param->{description
});
3327 PVE
::QemuConfig-
>write_config($vmid, $conf);
3330 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3335 __PACKAGE__-
>register_method({
3336 name
=> 'get_snapshot_config',
3337 path
=> '{vmid}/snapshot/{snapname}/config',
3340 description
=> "Get snapshot configuration",
3342 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3345 additionalProperties
=> 0,
3347 node
=> get_standard_option
('pve-node'),
3348 vmid
=> get_standard_option
('pve-vmid'),
3349 snapname
=> get_standard_option
('pve-snapshot-name'),
3352 returns
=> { type
=> "object" },
3356 my $rpcenv = PVE
::RPCEnvironment
::get
();
3358 my $authuser = $rpcenv->get_user();
3360 my $vmid = extract_param
($param, 'vmid');
3362 my $snapname = extract_param
($param, 'snapname');
3364 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3366 my $snap = $conf->{snapshots
}->{$snapname};
3368 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3373 __PACKAGE__-
>register_method({
3375 path
=> '{vmid}/snapshot/{snapname}/rollback',
3379 description
=> "Rollback VM state to specified snapshot.",
3381 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3384 additionalProperties
=> 0,
3386 node
=> get_standard_option
('pve-node'),
3387 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3388 snapname
=> get_standard_option
('pve-snapshot-name'),
3393 description
=> "the task ID.",
3398 my $rpcenv = PVE
::RPCEnvironment
::get
();
3400 my $authuser = $rpcenv->get_user();
3402 my $node = extract_param
($param, 'node');
3404 my $vmid = extract_param
($param, 'vmid');
3406 my $snapname = extract_param
($param, 'snapname');
3409 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3410 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3414 # hold migration lock, this makes sure that nobody create replication snapshots
3415 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3418 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3421 __PACKAGE__-
>register_method({
3422 name
=> 'delsnapshot',
3423 path
=> '{vmid}/snapshot/{snapname}',
3427 description
=> "Delete a VM snapshot.",
3429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3432 additionalProperties
=> 0,
3434 node
=> get_standard_option
('pve-node'),
3435 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3436 snapname
=> get_standard_option
('pve-snapshot-name'),
3440 description
=> "For removal from config file, even if removing disk snapshots fails.",
3446 description
=> "the task ID.",
3451 my $rpcenv = PVE
::RPCEnvironment
::get
();
3453 my $authuser = $rpcenv->get_user();
3455 my $node = extract_param
($param, 'node');
3457 my $vmid = extract_param
($param, 'vmid');
3459 my $snapname = extract_param
($param, 'snapname');
3462 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3463 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3466 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3469 __PACKAGE__-
>register_method({
3471 path
=> '{vmid}/template',
3475 description
=> "Create a Template.",
3477 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3478 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3481 additionalProperties
=> 0,
3483 node
=> get_standard_option
('pve-node'),
3484 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3488 description
=> "If you want to convert only 1 disk to base image.",
3489 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3494 returns
=> { type
=> 'null'},
3498 my $rpcenv = PVE
::RPCEnvironment
::get
();
3500 my $authuser = $rpcenv->get_user();
3502 my $node = extract_param
($param, 'node');
3504 my $vmid = extract_param
($param, 'vmid');
3506 my $disk = extract_param
($param, 'disk');
3508 my $updatefn = sub {
3510 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3512 PVE
::QemuConfig-
>check_lock($conf);
3514 die "unable to create template, because VM contains snapshots\n"
3515 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3517 die "you can't convert a template to a template\n"
3518 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3520 die "you can't convert a VM to template if VM is running\n"
3521 if PVE
::QemuServer
::check_running
($vmid);
3524 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3527 $conf->{template
} = 1;
3528 PVE
::QemuConfig-
>write_config($vmid, $conf);
3530 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3533 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);