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
;
29 use PVE
::API2
::Qemu
::Agent
;
32 if (!$ENV{PVE_GENERATING_DOCS
}) {
33 require PVE
::HA
::Env
::PVE2
;
34 import PVE
::HA
::Env
::PVE2
;
35 require PVE
::HA
::Config
;
36 import PVE
::HA
::Config
;
40 use Data
::Dumper
; # fixme: remove
42 use base
qw(PVE::RESTHandler);
44 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.";
46 my $resolve_cdrom_alias = sub {
49 if (my $value = $param->{cdrom
}) {
50 $value .= ",media=cdrom" if $value !~ m/media=/;
51 $param->{ide2
} = $value;
52 delete $param->{cdrom
};
56 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
57 my $check_storage_access = sub {
58 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
60 PVE
::QemuServer
::foreach_drive
($settings, sub {
61 my ($ds, $drive) = @_;
63 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
65 my $volid = $drive->{file
};
67 if (!$volid || $volid eq 'none') {
69 } elsif ($isCDROM && ($volid eq 'cdrom')) {
70 $rpcenv->check($authuser, "/", ['Sys.Console']);
71 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
72 my ($storeid, $size) = ($2 || $default_storage, $3);
73 die "no storage ID specified (and no default storage)\n" if !$storeid;
74 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
75 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
76 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
77 if !$scfg->{content
}->{images
};
79 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
83 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
84 if defined($settings->{vmstatestorage
});
87 my $check_storage_access_clone = sub {
88 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
92 PVE
::QemuServer
::foreach_drive
($conf, sub {
93 my ($ds, $drive) = @_;
95 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
97 my $volid = $drive->{file
};
99 return if !$volid || $volid eq 'none';
102 if ($volid eq 'cdrom') {
103 $rpcenv->check($authuser, "/", ['Sys.Console']);
105 # we simply allow access
106 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
107 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
108 $sharedvm = 0 if !$scfg->{shared
};
112 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
113 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
114 $sharedvm = 0 if !$scfg->{shared
};
116 $sid = $storage if $storage;
117 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
121 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
122 if defined($conf->{vmstatestorage
});
127 # Note: $pool is only needed when creating a VM, because pool permissions
128 # are automatically inherited if VM already exists inside a pool.
129 my $create_disks = sub {
130 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
137 my ($ds, $disk) = @_;
139 my $volid = $disk->{file
};
141 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
142 delete $disk->{size
};
143 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
144 } elsif ($volid =~ $NEW_DISK_RE) {
145 my ($storeid, $size) = ($2 || $default_storage, $3);
146 die "no storage ID specified (and no default storage)\n" if !$storeid;
147 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
148 my $fmt = $disk->{format
} || $defformat;
150 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
153 if ($ds eq 'efidisk0') {
154 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt);
156 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
158 push @$vollist, $volid;
159 $disk->{file
} = $volid;
160 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
161 delete $disk->{format
}; # no longer needed
162 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
165 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
167 my $volid_is_new = 1;
170 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
171 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
176 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
178 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
180 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
182 die "volume $volid does not exists\n" if !$size;
184 $disk->{size
} = $size;
187 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
191 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
193 # free allocated images on error
195 syslog
('err', "VM $vmid creating disks failed");
196 foreach my $volid (@$vollist) {
197 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
203 # modify vm config if everything went well
204 foreach my $ds (keys %$res) {
205 $conf->{$ds} = $res->{$ds};
222 my $memoryoptions = {
228 my $hwtypeoptions = {
240 my $generaloptions = {
247 'migrate_downtime' => 1,
248 'migrate_speed' => 1,
260 my $vmpoweroptions = {
267 'vmstatestorage' => 1,
270 my $check_vm_modify_config_perm = sub {
271 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
273 return 1 if $authuser eq 'root@pam';
275 foreach my $opt (@$key_list) {
276 # disk checks need to be done somewhere else
277 next if PVE
::QemuServer
::is_valid_drivename
($opt);
278 next if $opt eq 'cdrom';
279 next if $opt =~ m/^unused\d+$/;
281 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
282 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
283 } elsif ($memoryoptions->{$opt}) {
284 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
285 } elsif ($hwtypeoptions->{$opt}) {
286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
287 } elsif ($generaloptions->{$opt}) {
288 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
289 # special case for startup since it changes host behaviour
290 if ($opt eq 'startup') {
291 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
293 } elsif ($vmpoweroptions->{$opt}) {
294 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
295 } elsif ($diskoptions->{$opt}) {
296 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
297 } elsif ($opt =~ m/^net\d+$/) {
298 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
300 # catches usb\d+, hostpci\d+, args, lock, etc.
301 # new options will be checked here
302 die "only root can set '$opt' config\n";
309 __PACKAGE__-
>register_method({
313 description
=> "Virtual machine index (per node).",
315 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
319 protected
=> 1, # qemu pid files are only readable by root
321 additionalProperties
=> 0,
323 node
=> get_standard_option
('pve-node'),
327 description
=> "Determine the full status of active VMs.",
337 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
342 my $rpcenv = PVE
::RPCEnvironment
::get
();
343 my $authuser = $rpcenv->get_user();
345 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
348 foreach my $vmid (keys %$vmstatus) {
349 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
351 my $data = $vmstatus->{$vmid};
352 $data->{vmid
} = int($vmid);
361 __PACKAGE__-
>register_method({
365 description
=> "Create or restore a virtual machine.",
367 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
368 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
369 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
370 user
=> 'all', # check inside
375 additionalProperties
=> 0,
376 properties
=> PVE
::QemuServer
::json_config_properties
(
378 node
=> get_standard_option
('pve-node'),
379 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
381 description
=> "The backup file.",
385 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
387 storage
=> get_standard_option
('pve-storage-id', {
388 description
=> "Default storage.",
390 completion
=> \
&PVE
::QemuServer
::complete_storage
,
395 description
=> "Allow to overwrite existing VM.",
396 requires
=> 'archive',
401 description
=> "Assign a unique random ethernet address.",
402 requires
=> 'archive',
406 type
=> 'string', format
=> 'pve-poolid',
407 description
=> "Add the VM to the specified pool.",
417 my $rpcenv = PVE
::RPCEnvironment
::get
();
419 my $authuser = $rpcenv->get_user();
421 my $node = extract_param
($param, 'node');
423 my $vmid = extract_param
($param, 'vmid');
425 my $archive = extract_param
($param, 'archive');
427 my $storage = extract_param
($param, 'storage');
429 my $force = extract_param
($param, 'force');
431 my $unique = extract_param
($param, 'unique');
433 my $pool = extract_param
($param, 'pool');
435 my $filename = PVE
::QemuConfig-
>config_file($vmid);
437 my $storecfg = PVE
::Storage
::config
();
439 PVE
::Cluster
::check_cfs_quorum
();
441 if (defined($pool)) {
442 $rpcenv->check_pool_exist($pool);
445 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
446 if defined($storage);
448 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
450 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
452 } elsif ($archive && $force && (-f
$filename) &&
453 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
454 # OK: user has VM.Backup permissions, and want to restore an existing VM
460 &$resolve_cdrom_alias($param);
462 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
464 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
466 foreach my $opt (keys %$param) {
467 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
468 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
469 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
471 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
472 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
476 PVE
::QemuServer
::add_random_macs
($param);
478 my $keystr = join(' ', keys %$param);
479 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
481 if ($archive eq '-') {
482 die "pipe requires cli environment\n"
483 if $rpcenv->{type
} ne 'cli';
485 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
486 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
490 my $restorefn = sub {
491 my $vmlist = PVE
::Cluster
::get_vmlist
();
492 if ($vmlist->{ids
}->{$vmid}) {
493 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
494 if ($current_node eq $node) {
495 my $conf = PVE
::QemuConfig-
>load_config($vmid);
497 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
499 die "unable to restore vm $vmid - config file already exists\n"
502 die "unable to restore vm $vmid - vm is running\n"
503 if PVE
::QemuServer
::check_running
($vmid);
505 die "unable to restore vm $vmid - vm is a template\n"
506 if PVE
::QemuConfig-
>is_template($conf);
509 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
514 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
517 unique
=> $unique });
519 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
522 # ensure no old replication state are exists
523 PVE
::ReplicationState
::delete_guest_states
($vmid);
525 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
531 PVE
::Cluster
::check_vmid_unused
($vmid);
533 # ensure no old replication state are exists
534 PVE
::ReplicationState
::delete_guest_states
($vmid);
544 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
546 if (!$conf->{bootdisk
}) {
547 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
548 $conf->{bootdisk
} = $firstdisk if $firstdisk;
551 # auto generate uuid if user did not specify smbios1 option
552 if (!$conf->{smbios1
}) {
553 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
556 PVE
::QemuConfig-
>write_config($vmid, $conf);
562 foreach my $volid (@$vollist) {
563 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
566 die "create failed - $err";
569 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
572 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
575 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
578 __PACKAGE__-
>register_method({
583 description
=> "Directory index",
588 additionalProperties
=> 0,
590 node
=> get_standard_option
('pve-node'),
591 vmid
=> get_standard_option
('pve-vmid'),
599 subdir
=> { type
=> 'string' },
602 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
608 { subdir
=> 'config' },
609 { subdir
=> 'pending' },
610 { subdir
=> 'status' },
611 { subdir
=> 'unlink' },
612 { subdir
=> 'vncproxy' },
613 { subdir
=> 'termproxy' },
614 { subdir
=> 'migrate' },
615 { subdir
=> 'resize' },
616 { subdir
=> 'move' },
618 { subdir
=> 'rrddata' },
619 { subdir
=> 'monitor' },
620 { subdir
=> 'agent' },
621 { subdir
=> 'snapshot' },
622 { subdir
=> 'spiceproxy' },
623 { subdir
=> 'sendkey' },
624 { subdir
=> 'firewall' },
630 __PACKAGE__-
>register_method ({
631 subclass
=> "PVE::API2::Firewall::VM",
632 path
=> '{vmid}/firewall',
635 __PACKAGE__-
>register_method ({
636 subclass
=> "PVE::API2::Qemu::Agent",
637 path
=> '{vmid}/agent',
640 __PACKAGE__-
>register_method({
642 path
=> '{vmid}/rrd',
644 protected
=> 1, # fixme: can we avoid that?
646 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
648 description
=> "Read VM RRD statistics (returns PNG)",
650 additionalProperties
=> 0,
652 node
=> get_standard_option
('pve-node'),
653 vmid
=> get_standard_option
('pve-vmid'),
655 description
=> "Specify the time frame you are interested in.",
657 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
660 description
=> "The list of datasources you want to display.",
661 type
=> 'string', format
=> 'pve-configid-list',
664 description
=> "The RRD consolidation function",
666 enum
=> [ 'AVERAGE', 'MAX' ],
674 filename
=> { type
=> 'string' },
680 return PVE
::Cluster
::create_rrd_graph
(
681 "pve2-vm/$param->{vmid}", $param->{timeframe
},
682 $param->{ds
}, $param->{cf
});
686 __PACKAGE__-
>register_method({
688 path
=> '{vmid}/rrddata',
690 protected
=> 1, # fixme: can we avoid that?
692 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
694 description
=> "Read VM RRD statistics",
696 additionalProperties
=> 0,
698 node
=> get_standard_option
('pve-node'),
699 vmid
=> get_standard_option
('pve-vmid'),
701 description
=> "Specify the time frame you are interested in.",
703 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
706 description
=> "The RRD consolidation function",
708 enum
=> [ 'AVERAGE', 'MAX' ],
723 return PVE
::Cluster
::create_rrd_data
(
724 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
728 __PACKAGE__-
>register_method({
730 path
=> '{vmid}/config',
733 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
735 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
738 additionalProperties
=> 0,
740 node
=> get_standard_option
('pve-node'),
741 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
743 description
=> "Get current values (instead of pending values).",
755 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
762 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
764 delete $conf->{snapshots
};
766 if (!$param->{current
}) {
767 foreach my $opt (keys %{$conf->{pending
}}) {
768 next if $opt eq 'delete';
769 my $value = $conf->{pending
}->{$opt};
770 next if ref($value); # just to be sure
771 $conf->{$opt} = $value;
773 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
774 foreach my $opt (keys %$pending_delete_hash) {
775 delete $conf->{$opt} if $conf->{$opt};
779 delete $conf->{pending
};
784 __PACKAGE__-
>register_method({
785 name
=> 'vm_pending',
786 path
=> '{vmid}/pending',
789 description
=> "Get virtual machine configuration, including pending changes.",
791 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
794 additionalProperties
=> 0,
796 node
=> get_standard_option
('pve-node'),
797 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
806 description
=> "Configuration option name.",
810 description
=> "Current value.",
815 description
=> "Pending value.",
820 description
=> "Indicates a pending delete request if present and not 0. " .
821 "The value 2 indicates a force-delete request.",
833 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
835 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
839 foreach my $opt (keys %$conf) {
840 next if ref($conf->{$opt});
841 my $item = { key
=> $opt };
842 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
843 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
844 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
848 foreach my $opt (keys %{$conf->{pending
}}) {
849 next if $opt eq 'delete';
850 next if ref($conf->{pending
}->{$opt}); # just to be sure
851 next if defined($conf->{$opt});
852 my $item = { key
=> $opt };
853 $item->{pending
} = $conf->{pending
}->{$opt};
857 while (my ($opt, $force) = each %$pending_delete_hash) {
858 next if $conf->{pending
}->{$opt}; # just to be sure
859 next if $conf->{$opt};
860 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
867 # POST/PUT {vmid}/config implementation
869 # The original API used PUT (idempotent) an we assumed that all operations
870 # are fast. But it turned out that almost any configuration change can
871 # involve hot-plug actions, or disk alloc/free. Such actions can take long
872 # time to complete and have side effects (not idempotent).
874 # The new implementation uses POST and forks a worker process. We added
875 # a new option 'background_delay'. If specified we wait up to
876 # 'background_delay' second for the worker task to complete. It returns null
877 # if the task is finished within that time, else we return the UPID.
879 my $update_vm_api = sub {
880 my ($param, $sync) = @_;
882 my $rpcenv = PVE
::RPCEnvironment
::get
();
884 my $authuser = $rpcenv->get_user();
886 my $node = extract_param
($param, 'node');
888 my $vmid = extract_param
($param, 'vmid');
890 my $digest = extract_param
($param, 'digest');
892 my $background_delay = extract_param
($param, 'background_delay');
894 my @paramarr = (); # used for log message
895 foreach my $key (sort keys %$param) {
896 push @paramarr, "-$key", $param->{$key};
899 my $skiplock = extract_param
($param, 'skiplock');
900 raise_param_exc
({ skiplock
=> "Only root may use this option." })
901 if $skiplock && $authuser ne 'root@pam';
903 my $delete_str = extract_param
($param, 'delete');
905 my $revert_str = extract_param
($param, 'revert');
907 my $force = extract_param
($param, 'force');
909 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
911 my $storecfg = PVE
::Storage
::config
();
913 my $defaults = PVE
::QemuServer
::load_defaults
();
915 &$resolve_cdrom_alias($param);
917 # now try to verify all parameters
920 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
921 if (!PVE
::QemuServer
::option_exists
($opt)) {
922 raise_param_exc
({ revert
=> "unknown option '$opt'" });
925 raise_param_exc
({ delete => "you can't use '-$opt' and " .
926 "-revert $opt' at the same time" })
927 if defined($param->{$opt});
933 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
934 $opt = 'ide2' if $opt eq 'cdrom';
936 raise_param_exc
({ delete => "you can't use '-$opt' and " .
937 "-delete $opt' at the same time" })
938 if defined($param->{$opt});
940 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
941 "-revert $opt' at the same time" })
944 if (!PVE
::QemuServer
::option_exists
($opt)) {
945 raise_param_exc
({ delete => "unknown option '$opt'" });
951 my $repl_conf = PVE
::ReplicationConfig-
>new();
952 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
953 my $check_replication = sub {
955 return if !$is_replicated;
956 my $volid = $drive->{file
};
957 return if !$volid || !($drive->{replicate
}//1);
958 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
959 my ($storeid, $format);
960 if ($volid =~ $NEW_DISK_RE) {
962 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
964 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
965 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
967 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
968 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
969 return if $scfg->{shared
};
970 die "cannot add non-replicatable volume to a replicated VM\n";
973 foreach my $opt (keys %$param) {
974 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
976 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
977 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
978 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
979 $check_replication->($drive);
980 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
981 } elsif ($opt =~ m/^net(\d+)$/) {
983 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
984 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
988 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
990 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
992 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
996 my $conf = PVE
::QemuConfig-
>load_config($vmid);
998 die "checksum missmatch (file change by other user?)\n"
999 if $digest && $digest ne $conf->{digest
};
1001 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1003 foreach my $opt (keys %$revert) {
1004 if (defined($conf->{$opt})) {
1005 $param->{$opt} = $conf->{$opt};
1006 } elsif (defined($conf->{pending
}->{$opt})) {
1011 if ($param->{memory
} || defined($param->{balloon
})) {
1012 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1013 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1015 die "balloon value too large (must be smaller than assigned memory)\n"
1016 if $balloon && $balloon > $maxmem;
1019 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1023 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1025 # write updates to pending section
1027 my $modified = {}; # record what $option we modify
1029 foreach my $opt (@delete) {
1030 $modified->{$opt} = 1;
1031 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1032 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1033 warn "cannot delete '$opt' - not set in current configuration!\n";
1034 $modified->{$opt} = 0;
1038 if ($opt =~ m/^unused/) {
1039 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1040 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1041 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1042 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1043 delete $conf->{$opt};
1044 PVE
::QemuConfig-
>write_config($vmid, $conf);
1046 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1047 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1048 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1049 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1050 if defined($conf->{pending
}->{$opt});
1051 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1052 PVE
::QemuConfig-
>write_config($vmid, $conf);
1054 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1055 PVE
::QemuConfig-
>write_config($vmid, $conf);
1059 foreach my $opt (keys %$param) { # add/change
1060 $modified->{$opt} = 1;
1061 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1062 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1064 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1065 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1066 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1067 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1069 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1071 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1072 if defined($conf->{pending
}->{$opt});
1074 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1076 $conf->{pending
}->{$opt} = $param->{$opt};
1078 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1079 PVE
::QemuConfig-
>write_config($vmid, $conf);
1082 # remove pending changes when nothing changed
1083 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1084 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1085 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1087 return if !scalar(keys %{$conf->{pending
}});
1089 my $running = PVE
::QemuServer
::check_running
($vmid);
1091 # apply pending changes
1093 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1097 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1098 raise_param_exc
($errors) if scalar(keys %$errors);
1100 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1110 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1112 if ($background_delay) {
1114 # Note: It would be better to do that in the Event based HTTPServer
1115 # to avoid blocking call to sleep.
1117 my $end_time = time() + $background_delay;
1119 my $task = PVE
::Tools
::upid_decode
($upid);
1122 while (time() < $end_time) {
1123 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1125 sleep(1); # this gets interrupted when child process ends
1129 my $status = PVE
::Tools
::upid_read_status
($upid);
1130 return undef if $status eq 'OK';
1139 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1142 my $vm_config_perm_list = [
1147 'VM.Config.Network',
1149 'VM.Config.Options',
1152 __PACKAGE__-
>register_method({
1153 name
=> 'update_vm_async',
1154 path
=> '{vmid}/config',
1158 description
=> "Set virtual machine options (asynchrounous API).",
1160 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1163 additionalProperties
=> 0,
1164 properties
=> PVE
::QemuServer
::json_config_properties
(
1166 node
=> get_standard_option
('pve-node'),
1167 vmid
=> get_standard_option
('pve-vmid'),
1168 skiplock
=> get_standard_option
('skiplock'),
1170 type
=> 'string', format
=> 'pve-configid-list',
1171 description
=> "A list of settings you want to delete.",
1175 type
=> 'string', format
=> 'pve-configid-list',
1176 description
=> "Revert a pending change.",
1181 description
=> $opt_force_description,
1183 requires
=> 'delete',
1187 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1191 background_delay
=> {
1193 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1204 code
=> $update_vm_api,
1207 __PACKAGE__-
>register_method({
1208 name
=> 'update_vm',
1209 path
=> '{vmid}/config',
1213 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1215 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1218 additionalProperties
=> 0,
1219 properties
=> PVE
::QemuServer
::json_config_properties
(
1221 node
=> get_standard_option
('pve-node'),
1222 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1223 skiplock
=> get_standard_option
('skiplock'),
1225 type
=> 'string', format
=> 'pve-configid-list',
1226 description
=> "A list of settings you want to delete.",
1230 type
=> 'string', format
=> 'pve-configid-list',
1231 description
=> "Revert a pending change.",
1236 description
=> $opt_force_description,
1238 requires
=> 'delete',
1242 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1248 returns
=> { type
=> 'null' },
1251 &$update_vm_api($param, 1);
1257 __PACKAGE__-
>register_method({
1258 name
=> 'destroy_vm',
1263 description
=> "Destroy the vm (also delete all used/owned volumes).",
1265 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1268 additionalProperties
=> 0,
1270 node
=> get_standard_option
('pve-node'),
1271 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1272 skiplock
=> get_standard_option
('skiplock'),
1281 my $rpcenv = PVE
::RPCEnvironment
::get
();
1283 my $authuser = $rpcenv->get_user();
1285 my $vmid = $param->{vmid
};
1287 my $skiplock = $param->{skiplock
};
1288 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1289 if $skiplock && $authuser ne 'root@pam';
1292 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1294 my $storecfg = PVE
::Storage
::config
();
1296 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1298 die "unable to remove VM $vmid - used in HA resources\n"
1299 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1301 # do not allow destroy if there are replication jobs
1302 my $repl_conf = PVE
::ReplicationConfig-
>new();
1303 $repl_conf->check_for_existing_jobs($vmid);
1305 # early tests (repeat after locking)
1306 die "VM $vmid is running - destroy failed\n"
1307 if PVE
::QemuServer
::check_running
($vmid);
1312 syslog
('info', "destroy VM $vmid: $upid\n");
1314 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1316 PVE
::AccessControl
::remove_vm_access
($vmid);
1318 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1321 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1324 __PACKAGE__-
>register_method({
1326 path
=> '{vmid}/unlink',
1330 description
=> "Unlink/delete disk images.",
1332 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1335 additionalProperties
=> 0,
1337 node
=> get_standard_option
('pve-node'),
1338 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1340 type
=> 'string', format
=> 'pve-configid-list',
1341 description
=> "A list of disk IDs you want to delete.",
1345 description
=> $opt_force_description,
1350 returns
=> { type
=> 'null'},
1354 $param->{delete} = extract_param
($param, 'idlist');
1356 __PACKAGE__-
>update_vm($param);
1363 __PACKAGE__-
>register_method({
1365 path
=> '{vmid}/vncproxy',
1369 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1371 description
=> "Creates a TCP VNC proxy connections.",
1373 additionalProperties
=> 0,
1375 node
=> get_standard_option
('pve-node'),
1376 vmid
=> get_standard_option
('pve-vmid'),
1380 description
=> "starts websockify instead of vncproxy",
1385 additionalProperties
=> 0,
1387 user
=> { type
=> 'string' },
1388 ticket
=> { type
=> 'string' },
1389 cert
=> { type
=> 'string' },
1390 port
=> { type
=> 'integer' },
1391 upid
=> { type
=> 'string' },
1397 my $rpcenv = PVE
::RPCEnvironment
::get
();
1399 my $authuser = $rpcenv->get_user();
1401 my $vmid = $param->{vmid
};
1402 my $node = $param->{node
};
1403 my $websocket = $param->{websocket
};
1405 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1407 my $authpath = "/vms/$vmid";
1409 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1411 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1414 my ($remip, $family);
1417 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1418 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1419 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1420 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1422 $family = PVE
::Tools
::get_host_address_family
($node);
1425 my $port = PVE
::Tools
::next_vnc_port
($family);
1432 syslog
('info', "starting vnc proxy $upid\n");
1436 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1439 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1441 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1442 '-timeout', $timeout, '-authpath', $authpath,
1443 '-perm', 'Sys.Console'];
1445 if ($param->{websocket
}) {
1446 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1447 push @$cmd, '-notls', '-listen', 'localhost';
1450 push @$cmd, '-c', @$remcmd, @$termcmd;
1452 PVE
::Tools
::run_command
($cmd);
1456 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1458 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1460 my $sock = IO
::Socket
::IP-
>new(
1465 GetAddrInfoFlags
=> 0,
1466 ) or die "failed to create socket: $!\n";
1467 # Inside the worker we shouldn't have any previous alarms
1468 # running anyway...:
1470 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1472 accept(my $cli, $sock) or die "connection failed: $!\n";
1475 if (PVE
::Tools
::run_command
($cmd,
1476 output
=> '>&'.fileno($cli),
1477 input
=> '<&'.fileno($cli),
1480 die "Failed to run vncproxy.\n";
1487 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1489 PVE
::Tools
::wait_for_vnc_port
($port);
1500 __PACKAGE__-
>register_method({
1501 name
=> 'termproxy',
1502 path
=> '{vmid}/termproxy',
1506 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1508 description
=> "Creates a TCP proxy connections.",
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid'),
1517 enum
=> [qw(serial0 serial1 serial2 serial3)],
1518 description
=> "opens a serial terminal (defaults to display)",
1523 additionalProperties
=> 0,
1525 user
=> { type
=> 'string' },
1526 ticket
=> { type
=> 'string' },
1527 port
=> { type
=> 'integer' },
1528 upid
=> { type
=> 'string' },
1534 my $rpcenv = PVE
::RPCEnvironment
::get
();
1536 my $authuser = $rpcenv->get_user();
1538 my $vmid = $param->{vmid
};
1539 my $node = $param->{node
};
1540 my $serial = $param->{serial
};
1542 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1544 if (!defined($serial)) {
1545 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1546 $serial = $conf->{vga
};
1550 my $authpath = "/vms/$vmid";
1552 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1554 my ($remip, $family);
1556 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1557 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1559 $family = PVE
::Tools
::get_host_address_family
($node);
1562 my $port = PVE
::Tools
::next_vnc_port
($family);
1564 my $remcmd = $remip ?
1565 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1567 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1568 push @$termcmd, '-iface', $serial if $serial;
1573 syslog
('info', "starting qemu termproxy $upid\n");
1575 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1576 '--perm', 'VM.Console', '--'];
1577 push @$cmd, @$remcmd, @$termcmd;
1579 PVE
::Tools
::run_command
($cmd);
1582 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1584 PVE
::Tools
::wait_for_vnc_port
($port);
1594 __PACKAGE__-
>register_method({
1595 name
=> 'vncwebsocket',
1596 path
=> '{vmid}/vncwebsocket',
1599 description
=> "You also need to pass a valid ticket (vncticket).",
1600 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1602 description
=> "Opens a weksocket for VNC traffic.",
1604 additionalProperties
=> 0,
1606 node
=> get_standard_option
('pve-node'),
1607 vmid
=> get_standard_option
('pve-vmid'),
1609 description
=> "Ticket from previous call to vncproxy.",
1614 description
=> "Port number returned by previous vncproxy call.",
1624 port
=> { type
=> 'string' },
1630 my $rpcenv = PVE
::RPCEnvironment
::get
();
1632 my $authuser = $rpcenv->get_user();
1634 my $vmid = $param->{vmid
};
1635 my $node = $param->{node
};
1637 my $authpath = "/vms/$vmid";
1639 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1641 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1643 # Note: VNC ports are acessible from outside, so we do not gain any
1644 # security if we verify that $param->{port} belongs to VM $vmid. This
1645 # check is done by verifying the VNC ticket (inside VNC protocol).
1647 my $port = $param->{port
};
1649 return { port
=> $port };
1652 __PACKAGE__-
>register_method({
1653 name
=> 'spiceproxy',
1654 path
=> '{vmid}/spiceproxy',
1659 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1661 description
=> "Returns a SPICE configuration to connect to the VM.",
1663 additionalProperties
=> 0,
1665 node
=> get_standard_option
('pve-node'),
1666 vmid
=> get_standard_option
('pve-vmid'),
1667 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1670 returns
=> get_standard_option
('remote-viewer-config'),
1674 my $rpcenv = PVE
::RPCEnvironment
::get
();
1676 my $authuser = $rpcenv->get_user();
1678 my $vmid = $param->{vmid
};
1679 my $node = $param->{node
};
1680 my $proxy = $param->{proxy
};
1682 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1683 my $title = "VM $vmid";
1684 $title .= " - ". $conf->{name
} if $conf->{name
};
1686 my $port = PVE
::QemuServer
::spice_port
($vmid);
1688 my ($ticket, undef, $remote_viewer_config) =
1689 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1691 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1692 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1694 return $remote_viewer_config;
1697 __PACKAGE__-
>register_method({
1699 path
=> '{vmid}/status',
1702 description
=> "Directory index",
1707 additionalProperties
=> 0,
1709 node
=> get_standard_option
('pve-node'),
1710 vmid
=> get_standard_option
('pve-vmid'),
1718 subdir
=> { type
=> 'string' },
1721 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1727 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1730 { subdir
=> 'current' },
1731 { subdir
=> 'start' },
1732 { subdir
=> 'stop' },
1738 __PACKAGE__-
>register_method({
1739 name
=> 'vm_status',
1740 path
=> '{vmid}/status/current',
1743 protected
=> 1, # qemu pid files are only readable by root
1744 description
=> "Get virtual machine status.",
1746 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1749 additionalProperties
=> 0,
1751 node
=> get_standard_option
('pve-node'),
1752 vmid
=> get_standard_option
('pve-vmid'),
1755 returns
=> { type
=> 'object' },
1760 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1762 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1763 my $status = $vmstatus->{$param->{vmid
}};
1765 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1767 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1769 $status->{agent
} = 1 if $conf->{agent
};
1774 __PACKAGE__-
>register_method({
1776 path
=> '{vmid}/status/start',
1780 description
=> "Start virtual machine.",
1782 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1785 additionalProperties
=> 0,
1787 node
=> get_standard_option
('pve-node'),
1788 vmid
=> get_standard_option
('pve-vmid',
1789 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1790 skiplock
=> get_standard_option
('skiplock'),
1791 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1792 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1795 enum
=> ['secure', 'insecure'],
1796 description
=> "Migration traffic is encrypted using an SSH " .
1797 "tunnel by default. On secure, completely private networks " .
1798 "this can be disabled to increase performance.",
1801 migration_network
=> {
1802 type
=> 'string', format
=> 'CIDR',
1803 description
=> "CIDR of the (sub) network that is used for migration.",
1806 machine
=> get_standard_option
('pve-qm-machine'),
1808 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1820 my $rpcenv = PVE
::RPCEnvironment
::get
();
1822 my $authuser = $rpcenv->get_user();
1824 my $node = extract_param
($param, 'node');
1826 my $vmid = extract_param
($param, 'vmid');
1828 my $machine = extract_param
($param, 'machine');
1830 my $stateuri = extract_param
($param, 'stateuri');
1831 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1832 if $stateuri && $authuser ne 'root@pam';
1834 my $skiplock = extract_param
($param, 'skiplock');
1835 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1836 if $skiplock && $authuser ne 'root@pam';
1838 my $migratedfrom = extract_param
($param, 'migratedfrom');
1839 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1840 if $migratedfrom && $authuser ne 'root@pam';
1842 my $migration_type = extract_param
($param, 'migration_type');
1843 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1844 if $migration_type && $authuser ne 'root@pam';
1846 my $migration_network = extract_param
($param, 'migration_network');
1847 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1848 if $migration_network && $authuser ne 'root@pam';
1850 my $targetstorage = extract_param
($param, 'targetstorage');
1851 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1852 if $targetstorage && $authuser ne 'root@pam';
1854 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1855 if $targetstorage && !$migratedfrom;
1857 # read spice ticket from STDIN
1859 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1860 if (defined(my $line = <STDIN
>)) {
1862 $spice_ticket = $line;
1866 PVE
::Cluster
::check_cfs_quorum
();
1868 my $storecfg = PVE
::Storage
::config
();
1870 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1871 $rpcenv->{type
} ne 'ha') {
1876 my $service = "vm:$vmid";
1878 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1880 print "Requesting HA start for VM $vmid\n";
1882 PVE
::Tools
::run_command
($cmd);
1887 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1894 syslog
('info', "start VM $vmid: $upid\n");
1896 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1897 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1902 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1906 __PACKAGE__-
>register_method({
1908 path
=> '{vmid}/status/stop',
1912 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1913 "is akin to pulling the power plug of a running computer and may damage the VM data",
1915 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1918 additionalProperties
=> 0,
1920 node
=> get_standard_option
('pve-node'),
1921 vmid
=> get_standard_option
('pve-vmid',
1922 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1923 skiplock
=> get_standard_option
('skiplock'),
1924 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1926 description
=> "Wait maximal timeout seconds.",
1932 description
=> "Do not deactivate storage volumes.",
1945 my $rpcenv = PVE
::RPCEnvironment
::get
();
1947 my $authuser = $rpcenv->get_user();
1949 my $node = extract_param
($param, 'node');
1951 my $vmid = extract_param
($param, 'vmid');
1953 my $skiplock = extract_param
($param, 'skiplock');
1954 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1955 if $skiplock && $authuser ne 'root@pam';
1957 my $keepActive = extract_param
($param, 'keepActive');
1958 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1959 if $keepActive && $authuser ne 'root@pam';
1961 my $migratedfrom = extract_param
($param, 'migratedfrom');
1962 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1963 if $migratedfrom && $authuser ne 'root@pam';
1966 my $storecfg = PVE
::Storage
::config
();
1968 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1973 my $service = "vm:$vmid";
1975 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1977 print "Requesting HA stop for VM $vmid\n";
1979 PVE
::Tools
::run_command
($cmd);
1984 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1990 syslog
('info', "stop VM $vmid: $upid\n");
1992 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1993 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1998 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2002 __PACKAGE__-
>register_method({
2004 path
=> '{vmid}/status/reset',
2008 description
=> "Reset virtual machine.",
2010 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2013 additionalProperties
=> 0,
2015 node
=> get_standard_option
('pve-node'),
2016 vmid
=> get_standard_option
('pve-vmid',
2017 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2018 skiplock
=> get_standard_option
('skiplock'),
2027 my $rpcenv = PVE
::RPCEnvironment
::get
();
2029 my $authuser = $rpcenv->get_user();
2031 my $node = extract_param
($param, 'node');
2033 my $vmid = extract_param
($param, 'vmid');
2035 my $skiplock = extract_param
($param, 'skiplock');
2036 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2037 if $skiplock && $authuser ne 'root@pam';
2039 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2044 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2049 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2052 __PACKAGE__-
>register_method({
2053 name
=> 'vm_shutdown',
2054 path
=> '{vmid}/status/shutdown',
2058 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2059 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2061 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2064 additionalProperties
=> 0,
2066 node
=> get_standard_option
('pve-node'),
2067 vmid
=> get_standard_option
('pve-vmid',
2068 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2069 skiplock
=> get_standard_option
('skiplock'),
2071 description
=> "Wait maximal timeout seconds.",
2077 description
=> "Make sure the VM stops.",
2083 description
=> "Do not deactivate storage volumes.",
2096 my $rpcenv = PVE
::RPCEnvironment
::get
();
2098 my $authuser = $rpcenv->get_user();
2100 my $node = extract_param
($param, 'node');
2102 my $vmid = extract_param
($param, 'vmid');
2104 my $skiplock = extract_param
($param, 'skiplock');
2105 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2106 if $skiplock && $authuser ne 'root@pam';
2108 my $keepActive = extract_param
($param, 'keepActive');
2109 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2110 if $keepActive && $authuser ne 'root@pam';
2112 my $storecfg = PVE
::Storage
::config
();
2116 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2117 # otherwise, we will infer a shutdown command, but run into the timeout,
2118 # then when the vm is resumed, it will instantly shutdown
2120 # checking the qmp status here to get feedback to the gui/cli/api
2121 # and the status query should not take too long
2124 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2128 if (!$err && $qmpstatus->{status
} eq "paused") {
2129 if ($param->{forceStop
}) {
2130 warn "VM is paused - stop instead of shutdown\n";
2133 die "VM is paused - cannot shutdown\n";
2137 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2138 ($rpcenv->{type
} ne 'ha')) {
2143 my $service = "vm:$vmid";
2145 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2147 print "Requesting HA stop for VM $vmid\n";
2149 PVE
::Tools
::run_command
($cmd);
2154 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2161 syslog
('info', "shutdown VM $vmid: $upid\n");
2163 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2164 $shutdown, $param->{forceStop
}, $keepActive);
2169 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2173 __PACKAGE__-
>register_method({
2174 name
=> 'vm_suspend',
2175 path
=> '{vmid}/status/suspend',
2179 description
=> "Suspend virtual machine.",
2181 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2184 additionalProperties
=> 0,
2186 node
=> get_standard_option
('pve-node'),
2187 vmid
=> get_standard_option
('pve-vmid',
2188 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2189 skiplock
=> get_standard_option
('skiplock'),
2198 my $rpcenv = PVE
::RPCEnvironment
::get
();
2200 my $authuser = $rpcenv->get_user();
2202 my $node = extract_param
($param, 'node');
2204 my $vmid = extract_param
($param, 'vmid');
2206 my $skiplock = extract_param
($param, 'skiplock');
2207 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2208 if $skiplock && $authuser ne 'root@pam';
2210 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2215 syslog
('info', "suspend VM $vmid: $upid\n");
2217 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2222 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2225 __PACKAGE__-
>register_method({
2226 name
=> 'vm_resume',
2227 path
=> '{vmid}/status/resume',
2231 description
=> "Resume virtual machine.",
2233 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2236 additionalProperties
=> 0,
2238 node
=> get_standard_option
('pve-node'),
2239 vmid
=> get_standard_option
('pve-vmid',
2240 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2241 skiplock
=> get_standard_option
('skiplock'),
2242 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2252 my $rpcenv = PVE
::RPCEnvironment
::get
();
2254 my $authuser = $rpcenv->get_user();
2256 my $node = extract_param
($param, 'node');
2258 my $vmid = extract_param
($param, 'vmid');
2260 my $skiplock = extract_param
($param, 'skiplock');
2261 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2262 if $skiplock && $authuser ne 'root@pam';
2264 my $nocheck = extract_param
($param, 'nocheck');
2266 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2271 syslog
('info', "resume VM $vmid: $upid\n");
2273 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2278 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2281 __PACKAGE__-
>register_method({
2282 name
=> 'vm_sendkey',
2283 path
=> '{vmid}/sendkey',
2287 description
=> "Send key event to virtual machine.",
2289 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2292 additionalProperties
=> 0,
2294 node
=> get_standard_option
('pve-node'),
2295 vmid
=> get_standard_option
('pve-vmid',
2296 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2297 skiplock
=> get_standard_option
('skiplock'),
2299 description
=> "The key (qemu monitor encoding).",
2304 returns
=> { type
=> 'null'},
2308 my $rpcenv = PVE
::RPCEnvironment
::get
();
2310 my $authuser = $rpcenv->get_user();
2312 my $node = extract_param
($param, 'node');
2314 my $vmid = extract_param
($param, 'vmid');
2316 my $skiplock = extract_param
($param, 'skiplock');
2317 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2318 if $skiplock && $authuser ne 'root@pam';
2320 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2325 __PACKAGE__-
>register_method({
2326 name
=> 'vm_feature',
2327 path
=> '{vmid}/feature',
2331 description
=> "Check if feature for virtual machine is available.",
2333 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2336 additionalProperties
=> 0,
2338 node
=> get_standard_option
('pve-node'),
2339 vmid
=> get_standard_option
('pve-vmid'),
2341 description
=> "Feature to check.",
2343 enum
=> [ 'snapshot', 'clone', 'copy' ],
2345 snapname
=> get_standard_option
('pve-snapshot-name', {
2353 hasFeature
=> { type
=> 'boolean' },
2356 items
=> { type
=> 'string' },
2363 my $node = extract_param
($param, 'node');
2365 my $vmid = extract_param
($param, 'vmid');
2367 my $snapname = extract_param
($param, 'snapname');
2369 my $feature = extract_param
($param, 'feature');
2371 my $running = PVE
::QemuServer
::check_running
($vmid);
2373 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2376 my $snap = $conf->{snapshots
}->{$snapname};
2377 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2380 my $storecfg = PVE
::Storage
::config
();
2382 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2383 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2386 hasFeature
=> $hasFeature,
2387 nodes
=> [ keys %$nodelist ],
2391 __PACKAGE__-
>register_method({
2393 path
=> '{vmid}/clone',
2397 description
=> "Create a copy of virtual machine/template.",
2399 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2400 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2401 "'Datastore.AllocateSpace' on any used storage.",
2404 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2406 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2407 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2412 additionalProperties
=> 0,
2414 node
=> get_standard_option
('pve-node'),
2415 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2416 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2419 type
=> 'string', format
=> 'dns-name',
2420 description
=> "Set a name for the new VM.",
2425 description
=> "Description for the new VM.",
2429 type
=> 'string', format
=> 'pve-poolid',
2430 description
=> "Add the new VM to the specified pool.",
2432 snapname
=> get_standard_option
('pve-snapshot-name', {
2435 storage
=> get_standard_option
('pve-storage-id', {
2436 description
=> "Target storage for full clone.",
2441 description
=> "Target format for file storage.",
2445 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2450 description
=> "Create a full copy of all disk. This is always done when " .
2451 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2454 target
=> get_standard_option
('pve-node', {
2455 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2466 my $rpcenv = PVE
::RPCEnvironment
::get
();
2468 my $authuser = $rpcenv->get_user();
2470 my $node = extract_param
($param, 'node');
2472 my $vmid = extract_param
($param, 'vmid');
2474 my $newid = extract_param
($param, 'newid');
2476 my $pool = extract_param
($param, 'pool');
2478 if (defined($pool)) {
2479 $rpcenv->check_pool_exist($pool);
2482 my $snapname = extract_param
($param, 'snapname');
2484 my $storage = extract_param
($param, 'storage');
2486 my $format = extract_param
($param, 'format');
2488 my $target = extract_param
($param, 'target');
2490 my $localnode = PVE
::INotify
::nodename
();
2492 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2494 PVE
::Cluster
::check_node_exists
($target) if $target;
2496 my $storecfg = PVE
::Storage
::config
();
2499 # check if storage is enabled on local node
2500 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2502 # check if storage is available on target node
2503 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2504 # clone only works if target storage is shared
2505 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2506 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2510 PVE
::Cluster
::check_cfs_quorum
();
2512 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2514 # exclusive lock if VM is running - else shared lock is enough;
2515 my $shared_lock = $running ?
0 : 1;
2519 # do all tests after lock
2520 # we also try to do all tests before we fork the worker
2522 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2524 PVE
::QemuConfig-
>check_lock($conf);
2526 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2528 die "unexpected state change\n" if $verify_running != $running;
2530 die "snapshot '$snapname' does not exist\n"
2531 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2533 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2535 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2537 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2539 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2541 die "unable to create VM $newid: config file already exists\n"
2544 my $newconf = { lock => 'clone' };
2549 foreach my $opt (keys %$oldconf) {
2550 my $value = $oldconf->{$opt};
2552 # do not copy snapshot related info
2553 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2554 $opt eq 'vmstate' || $opt eq 'snapstate';
2556 # no need to copy unused images, because VMID(owner) changes anyways
2557 next if $opt =~ m/^unused\d+$/;
2559 # always change MAC! address
2560 if ($opt =~ m/^net(\d+)$/) {
2561 my $net = PVE
::QemuServer
::parse_net
($value);
2562 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2563 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2564 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2565 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2566 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2567 die "unable to parse drive options for '$opt'\n" if !$drive;
2568 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2569 $newconf->{$opt} = $value; # simply copy configuration
2571 if ($param->{full
}) {
2572 die "Full clone feature is not supported for drive '$opt'\n"
2573 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2574 $fullclone->{$opt} = 1;
2576 # not full means clone instead of copy
2577 die "Linked clone feature is not supported for drive '$opt'\n"
2578 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2580 $drives->{$opt} = $drive;
2581 push @$vollist, $drive->{file
};
2584 # copy everything else
2585 $newconf->{$opt} = $value;
2589 # auto generate a new uuid
2590 my ($uuid, $uuid_str);
2591 UUID
::generate
($uuid);
2592 UUID
::unparse
($uuid, $uuid_str);
2593 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2594 $smbios1->{uuid
} = $uuid_str;
2595 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2597 delete $newconf->{template
};
2599 if ($param->{name
}) {
2600 $newconf->{name
} = $param->{name
};
2602 if ($oldconf->{name
}) {
2603 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2605 $newconf->{name
} = "Copy-of-VM-$vmid";
2609 if ($param->{description
}) {
2610 $newconf->{description
} = $param->{description
};
2613 # create empty/temp config - this fails if VM already exists on other node
2614 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2619 my $newvollist = [];
2626 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2628 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2630 my $total_jobs = scalar(keys %{$drives});
2633 foreach my $opt (keys %$drives) {
2634 my $drive = $drives->{$opt};
2635 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2637 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2638 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2639 $jobs, $skipcomplete, $oldconf->{agent
});
2641 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2643 PVE
::QemuConfig-
>write_config($newid, $newconf);
2647 delete $newconf->{lock};
2648 PVE
::QemuConfig-
>write_config($newid, $newconf);
2651 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2652 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2653 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2655 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2656 die "Failed to move config to node '$target' - rename failed: $!\n"
2657 if !rename($conffile, $newconffile);
2660 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2665 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2667 sleep 1; # some storage like rbd need to wait before release volume - really?
2669 foreach my $volid (@$newvollist) {
2670 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2673 die "clone failed: $err";
2679 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2681 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2684 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2685 # Aquire exclusive lock lock for $newid
2686 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2691 __PACKAGE__-
>register_method({
2692 name
=> 'move_vm_disk',
2693 path
=> '{vmid}/move_disk',
2697 description
=> "Move volume to different storage.",
2699 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2701 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2702 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2706 additionalProperties
=> 0,
2708 node
=> get_standard_option
('pve-node'),
2709 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2712 description
=> "The disk you want to move.",
2713 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2715 storage
=> get_standard_option
('pve-storage-id', {
2716 description
=> "Target storage.",
2717 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2721 description
=> "Target Format.",
2722 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2727 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2733 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2741 description
=> "the task ID.",
2746 my $rpcenv = PVE
::RPCEnvironment
::get
();
2748 my $authuser = $rpcenv->get_user();
2750 my $node = extract_param
($param, 'node');
2752 my $vmid = extract_param
($param, 'vmid');
2754 my $digest = extract_param
($param, 'digest');
2756 my $disk = extract_param
($param, 'disk');
2758 my $storeid = extract_param
($param, 'storage');
2760 my $format = extract_param
($param, 'format');
2762 my $storecfg = PVE
::Storage
::config
();
2764 my $updatefn = sub {
2766 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2768 PVE
::QemuConfig-
>check_lock($conf);
2770 die "checksum missmatch (file change by other user?)\n"
2771 if $digest && $digest ne $conf->{digest
};
2773 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2775 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2777 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2779 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2782 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2783 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2787 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2788 (!$format || !$oldfmt || $oldfmt eq $format);
2790 # this only checks snapshots because $disk is passed!
2791 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2792 die "you can't move a disk with snapshots and delete the source\n"
2793 if $snapshotted && $param->{delete};
2795 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2797 my $running = PVE
::QemuServer
::check_running
($vmid);
2799 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2803 my $newvollist = [];
2809 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2811 warn "moving disk with snapshots, snapshots will not be moved!\n"
2814 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2815 $vmid, $storeid, $format, 1, $newvollist);
2817 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2819 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2821 # convert moved disk to base if part of template
2822 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2823 if PVE
::QemuConfig-
>is_template($conf);
2825 PVE
::QemuConfig-
>write_config($vmid, $conf);
2828 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2829 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2836 foreach my $volid (@$newvollist) {
2837 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2840 die "storage migration failed: $err";
2843 if ($param->{delete}) {
2845 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2846 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2852 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2855 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2858 __PACKAGE__-
>register_method({
2859 name
=> 'migrate_vm',
2860 path
=> '{vmid}/migrate',
2864 description
=> "Migrate virtual machine. Creates a new migration task.",
2866 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2869 additionalProperties
=> 0,
2871 node
=> get_standard_option
('pve-node'),
2872 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2873 target
=> get_standard_option
('pve-node', {
2874 description
=> "Target node.",
2875 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2879 description
=> "Use online/live migration.",
2884 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2889 enum
=> ['secure', 'insecure'],
2890 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2893 migration_network
=> {
2894 type
=> 'string', format
=> 'CIDR',
2895 description
=> "CIDR of the (sub) network that is used for migration.",
2898 "with-local-disks" => {
2900 description
=> "Enable live storage migration for local disk",
2903 targetstorage
=> get_standard_option
('pve-storage-id', {
2904 description
=> "Default target storage.",
2906 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2912 description
=> "the task ID.",
2917 my $rpcenv = PVE
::RPCEnvironment
::get
();
2919 my $authuser = $rpcenv->get_user();
2921 my $target = extract_param
($param, 'target');
2923 my $localnode = PVE
::INotify
::nodename
();
2924 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2926 PVE
::Cluster
::check_cfs_quorum
();
2928 PVE
::Cluster
::check_node_exists
($target);
2930 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2932 my $vmid = extract_param
($param, 'vmid');
2934 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2935 if !$param->{online
} && $param->{targetstorage
};
2937 raise_param_exc
({ force
=> "Only root may use this option." })
2938 if $param->{force
} && $authuser ne 'root@pam';
2940 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2941 if $param->{migration_type
} && $authuser ne 'root@pam';
2943 # allow root only until better network permissions are available
2944 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2945 if $param->{migration_network
} && $authuser ne 'root@pam';
2948 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2950 # try to detect errors early
2952 PVE
::QemuConfig-
>check_lock($conf);
2954 if (PVE
::QemuServer
::check_running
($vmid)) {
2955 die "cant migrate running VM without --online\n"
2956 if !$param->{online
};
2959 my $storecfg = PVE
::Storage
::config
();
2961 if( $param->{targetstorage
}) {
2962 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2964 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2967 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2972 my $service = "vm:$vmid";
2974 my $cmd = ['ha-manager', 'migrate', $service, $target];
2976 print "Requesting HA migration for VM $vmid to node $target\n";
2978 PVE
::Tools
::run_command
($cmd);
2983 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2988 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2992 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2995 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3000 __PACKAGE__-
>register_method({
3002 path
=> '{vmid}/monitor',
3006 description
=> "Execute Qemu monitor commands.",
3008 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3009 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3012 additionalProperties
=> 0,
3014 node
=> get_standard_option
('pve-node'),
3015 vmid
=> get_standard_option
('pve-vmid'),
3018 description
=> "The monitor command.",
3022 returns
=> { type
=> 'string'},
3026 my $rpcenv = PVE
::RPCEnvironment
::get
();
3027 my $authuser = $rpcenv->get_user();
3030 my $command = shift;
3031 return $command =~ m/^\s*info(\s+|$)/
3032 || $command =~ m/^\s*help\s*$/;
3035 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3036 if !&$is_ro($param->{command
});
3038 my $vmid = $param->{vmid
};
3040 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3044 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3046 $res = "ERROR: $@" if $@;
3051 __PACKAGE__-
>register_method({
3052 name
=> 'resize_vm',
3053 path
=> '{vmid}/resize',
3057 description
=> "Extend volume size.",
3059 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3062 additionalProperties
=> 0,
3064 node
=> get_standard_option
('pve-node'),
3065 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3066 skiplock
=> get_standard_option
('skiplock'),
3069 description
=> "The disk you want to resize.",
3070 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3074 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3075 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.",
3079 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3085 returns
=> { type
=> 'null'},
3089 my $rpcenv = PVE
::RPCEnvironment
::get
();
3091 my $authuser = $rpcenv->get_user();
3093 my $node = extract_param
($param, 'node');
3095 my $vmid = extract_param
($param, 'vmid');
3097 my $digest = extract_param
($param, 'digest');
3099 my $disk = extract_param
($param, 'disk');
3101 my $sizestr = extract_param
($param, 'size');
3103 my $skiplock = extract_param
($param, 'skiplock');
3104 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3105 if $skiplock && $authuser ne 'root@pam';
3107 my $storecfg = PVE
::Storage
::config
();
3109 my $updatefn = sub {
3111 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3113 die "checksum missmatch (file change by other user?)\n"
3114 if $digest && $digest ne $conf->{digest
};
3115 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3117 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3119 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3121 my (undef, undef, undef, undef, undef, undef, $format) =
3122 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3124 die "can't resize volume: $disk if snapshot exists\n"
3125 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3127 my $volid = $drive->{file
};
3129 die "disk '$disk' has no associated volume\n" if !$volid;
3131 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3133 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3135 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3137 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3138 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3140 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3141 my ($ext, $newsize, $unit) = ($1, $2, $4);
3144 $newsize = $newsize * 1024;
3145 } elsif ($unit eq 'M') {
3146 $newsize = $newsize * 1024 * 1024;
3147 } elsif ($unit eq 'G') {
3148 $newsize = $newsize * 1024 * 1024 * 1024;
3149 } elsif ($unit eq 'T') {
3150 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3153 $newsize += $size if $ext;
3154 $newsize = int($newsize);
3156 die "shrinking disks is not supported\n" if $newsize < $size;
3158 return if $size == $newsize;
3160 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3162 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3164 $drive->{size
} = $newsize;
3165 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3167 PVE
::QemuConfig-
>write_config($vmid, $conf);
3170 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3174 __PACKAGE__-
>register_method({
3175 name
=> 'snapshot_list',
3176 path
=> '{vmid}/snapshot',
3178 description
=> "List all snapshots.",
3180 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3183 protected
=> 1, # qemu pid files are only readable by root
3185 additionalProperties
=> 0,
3187 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3188 node
=> get_standard_option
('pve-node'),
3197 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3202 my $vmid = $param->{vmid
};
3204 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3205 my $snaphash = $conf->{snapshots
} || {};
3209 foreach my $name (keys %$snaphash) {
3210 my $d = $snaphash->{$name};
3213 snaptime
=> $d->{snaptime
} || 0,
3214 vmstate
=> $d->{vmstate
} ?
1 : 0,
3215 description
=> $d->{description
} || '',
3217 $item->{parent
} = $d->{parent
} if $d->{parent
};
3218 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3222 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3223 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3224 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3226 push @$res, $current;
3231 __PACKAGE__-
>register_method({
3233 path
=> '{vmid}/snapshot',
3237 description
=> "Snapshot a VM.",
3239 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3242 additionalProperties
=> 0,
3244 node
=> get_standard_option
('pve-node'),
3245 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3246 snapname
=> get_standard_option
('pve-snapshot-name'),
3250 description
=> "Save the vmstate",
3255 description
=> "A textual description or comment.",
3261 description
=> "the task ID.",
3266 my $rpcenv = PVE
::RPCEnvironment
::get
();
3268 my $authuser = $rpcenv->get_user();
3270 my $node = extract_param
($param, 'node');
3272 my $vmid = extract_param
($param, 'vmid');
3274 my $snapname = extract_param
($param, 'snapname');
3276 die "unable to use snapshot name 'current' (reserved name)\n"
3277 if $snapname eq 'current';
3280 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3281 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3282 $param->{description
});
3285 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3288 __PACKAGE__-
>register_method({
3289 name
=> 'snapshot_cmd_idx',
3290 path
=> '{vmid}/snapshot/{snapname}',
3297 additionalProperties
=> 0,
3299 vmid
=> get_standard_option
('pve-vmid'),
3300 node
=> get_standard_option
('pve-node'),
3301 snapname
=> get_standard_option
('pve-snapshot-name'),
3310 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3317 push @$res, { cmd
=> 'rollback' };
3318 push @$res, { cmd
=> 'config' };
3323 __PACKAGE__-
>register_method({
3324 name
=> 'update_snapshot_config',
3325 path
=> '{vmid}/snapshot/{snapname}/config',
3329 description
=> "Update snapshot metadata.",
3331 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3334 additionalProperties
=> 0,
3336 node
=> get_standard_option
('pve-node'),
3337 vmid
=> get_standard_option
('pve-vmid'),
3338 snapname
=> get_standard_option
('pve-snapshot-name'),
3342 description
=> "A textual description or comment.",
3346 returns
=> { type
=> 'null' },
3350 my $rpcenv = PVE
::RPCEnvironment
::get
();
3352 my $authuser = $rpcenv->get_user();
3354 my $vmid = extract_param
($param, 'vmid');
3356 my $snapname = extract_param
($param, 'snapname');
3358 return undef if !defined($param->{description
});
3360 my $updatefn = sub {
3362 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3364 PVE
::QemuConfig-
>check_lock($conf);
3366 my $snap = $conf->{snapshots
}->{$snapname};
3368 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3370 $snap->{description
} = $param->{description
} if defined($param->{description
});
3372 PVE
::QemuConfig-
>write_config($vmid, $conf);
3375 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3380 __PACKAGE__-
>register_method({
3381 name
=> 'get_snapshot_config',
3382 path
=> '{vmid}/snapshot/{snapname}/config',
3385 description
=> "Get snapshot configuration",
3387 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3390 additionalProperties
=> 0,
3392 node
=> get_standard_option
('pve-node'),
3393 vmid
=> get_standard_option
('pve-vmid'),
3394 snapname
=> get_standard_option
('pve-snapshot-name'),
3397 returns
=> { type
=> "object" },
3401 my $rpcenv = PVE
::RPCEnvironment
::get
();
3403 my $authuser = $rpcenv->get_user();
3405 my $vmid = extract_param
($param, 'vmid');
3407 my $snapname = extract_param
($param, 'snapname');
3409 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3411 my $snap = $conf->{snapshots
}->{$snapname};
3413 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3418 __PACKAGE__-
>register_method({
3420 path
=> '{vmid}/snapshot/{snapname}/rollback',
3424 description
=> "Rollback VM state to specified snapshot.",
3426 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3429 additionalProperties
=> 0,
3431 node
=> get_standard_option
('pve-node'),
3432 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3433 snapname
=> get_standard_option
('pve-snapshot-name'),
3438 description
=> "the task ID.",
3443 my $rpcenv = PVE
::RPCEnvironment
::get
();
3445 my $authuser = $rpcenv->get_user();
3447 my $node = extract_param
($param, 'node');
3449 my $vmid = extract_param
($param, 'vmid');
3451 my $snapname = extract_param
($param, 'snapname');
3454 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3455 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3459 # hold migration lock, this makes sure that nobody create replication snapshots
3460 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3463 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3466 __PACKAGE__-
>register_method({
3467 name
=> 'delsnapshot',
3468 path
=> '{vmid}/snapshot/{snapname}',
3472 description
=> "Delete a VM snapshot.",
3474 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3477 additionalProperties
=> 0,
3479 node
=> get_standard_option
('pve-node'),
3480 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3481 snapname
=> get_standard_option
('pve-snapshot-name'),
3485 description
=> "For removal from config file, even if removing disk snapshots fails.",
3491 description
=> "the task ID.",
3496 my $rpcenv = PVE
::RPCEnvironment
::get
();
3498 my $authuser = $rpcenv->get_user();
3500 my $node = extract_param
($param, 'node');
3502 my $vmid = extract_param
($param, 'vmid');
3504 my $snapname = extract_param
($param, 'snapname');
3507 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3508 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3511 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3514 __PACKAGE__-
>register_method({
3516 path
=> '{vmid}/template',
3520 description
=> "Create a Template.",
3522 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3523 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3526 additionalProperties
=> 0,
3528 node
=> get_standard_option
('pve-node'),
3529 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3533 description
=> "If you want to convert only 1 disk to base image.",
3534 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3539 returns
=> { type
=> 'null'},
3543 my $rpcenv = PVE
::RPCEnvironment
::get
();
3545 my $authuser = $rpcenv->get_user();
3547 my $node = extract_param
($param, 'node');
3549 my $vmid = extract_param
($param, 'vmid');
3551 my $disk = extract_param
($param, 'disk');
3553 my $updatefn = sub {
3555 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3557 PVE
::QemuConfig-
>check_lock($conf);
3559 die "unable to create template, because VM contains snapshots\n"
3560 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3562 die "you can't convert a template to a template\n"
3563 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3565 die "you can't convert a VM to template if VM is running\n"
3566 if PVE
::QemuServer
::check_running
($vmid);
3569 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3572 $conf->{template
} = 1;
3573 PVE
::QemuConfig-
>write_config($vmid, $conf);
3575 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3578 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);