1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
13 use PVE
::Tools
qw(extract_param);
14 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
16 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::ReplicationConfig
;
19 use PVE
::GuestHelpers
;
23 use PVE
::RPCEnvironment
;
24 use PVE
::AccessControl
;
28 use PVE
::API2
::Firewall
::VM
;
31 if (!$ENV{PVE_GENERATING_DOCS
}) {
32 require PVE
::HA
::Env
::PVE2
;
33 import PVE
::HA
::Env
::PVE2
;
34 require PVE
::HA
::Config
;
35 import PVE
::HA
::Config
;
39 use Data
::Dumper
; # fixme: remove
41 use base
qw(PVE::RESTHandler);
43 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
45 my $resolve_cdrom_alias = sub {
48 if (my $value = $param->{cdrom
}) {
49 $value .= ",media=cdrom" if $value !~ m/media=/;
50 $param->{ide2
} = $value;
51 delete $param->{cdrom
};
55 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
56 my $check_storage_access = sub {
57 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
59 PVE
::QemuServer
::foreach_drive
($settings, sub {
60 my ($ds, $drive) = @_;
62 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
64 my $volid = $drive->{file
};
66 if (!$volid || $volid eq 'none') {
68 } elsif ($isCDROM && ($volid eq 'cdrom')) {
69 $rpcenv->check($authuser, "/", ['Sys.Console']);
70 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
71 my ($storeid, $size) = ($2 || $default_storage, $3);
72 die "no storage ID specified (and no default storage)\n" if !$storeid;
73 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
74 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
75 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
76 if !$scfg->{content
}->{images
};
78 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
82 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
83 if defined($settings->{vmstatestorage
});
86 my $check_storage_access_clone = sub {
87 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
91 PVE
::QemuServer
::foreach_drive
($conf, sub {
92 my ($ds, $drive) = @_;
94 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
96 my $volid = $drive->{file
};
98 return if !$volid || $volid eq 'none';
101 if ($volid eq 'cdrom') {
102 $rpcenv->check($authuser, "/", ['Sys.Console']);
104 # we simply allow access
105 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
106 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
107 $sharedvm = 0 if !$scfg->{shared
};
111 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
112 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
113 $sharedvm = 0 if !$scfg->{shared
};
115 $sid = $storage if $storage;
116 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
120 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
121 if defined($conf->{vmstatestorage
});
126 # Note: $pool is only needed when creating a VM, because pool permissions
127 # are automatically inherited if VM already exists inside a pool.
128 my $create_disks = sub {
129 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
136 my ($ds, $disk) = @_;
138 my $volid = $disk->{file
};
140 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
141 delete $disk->{size
};
142 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
143 } elsif ($volid =~ $NEW_DISK_RE) {
144 my ($storeid, $size) = ($2 || $default_storage, $3);
145 die "no storage ID specified (and no default storage)\n" if !$storeid;
146 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
147 my $fmt = $disk->{format
} || $defformat;
149 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
152 if ($ds eq 'efidisk0') {
153 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt);
155 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
157 push @$vollist, $volid;
158 $disk->{file
} = $volid;
159 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
160 delete $disk->{format
}; # no longer needed
161 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
164 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
166 my $volid_is_new = 1;
169 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
170 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
175 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
177 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
179 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
181 die "volume $volid does not exists\n" if !$size;
183 $disk->{size
} = $size;
186 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
190 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
192 # free allocated images on error
194 syslog
('err', "VM $vmid creating disks failed");
195 foreach my $volid (@$vollist) {
196 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
202 # modify vm config if everything went well
203 foreach my $ds (keys %$res) {
204 $conf->{$ds} = $res->{$ds};
221 my $memoryoptions = {
227 my $hwtypeoptions = {
239 my $generaloptions = {
246 'migrate_downtime' => 1,
247 'migrate_speed' => 1,
259 my $vmpoweroptions = {
266 'vmstatestorage' => 1,
269 my $check_vm_modify_config_perm = sub {
270 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
272 return 1 if $authuser eq 'root@pam';
274 foreach my $opt (@$key_list) {
275 # disk checks need to be done somewhere else
276 next if PVE
::QemuServer
::is_valid_drivename
($opt);
277 next if $opt eq 'cdrom';
278 next if $opt =~ m/^unused\d+$/;
280 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
281 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
282 } elsif ($memoryoptions->{$opt}) {
283 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
284 } elsif ($hwtypeoptions->{$opt}) {
285 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
286 } elsif ($generaloptions->{$opt}) {
287 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
288 # special case for startup since it changes host behaviour
289 if ($opt eq 'startup') {
290 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
292 } elsif ($vmpoweroptions->{$opt}) {
293 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
294 } elsif ($diskoptions->{$opt}) {
295 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
296 } elsif ($opt =~ m/^net\d+$/) {
297 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
299 # catches usb\d+, hostpci\d+, args, lock, etc.
300 # new options will be checked here
301 die "only root can set '$opt' config\n";
308 __PACKAGE__-
>register_method({
312 description
=> "Virtual machine index (per node).",
314 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
318 protected
=> 1, # qemu pid files are only readable by root
320 additionalProperties
=> 0,
322 node
=> get_standard_option
('pve-node'),
326 description
=> "Determine the full status of active VMs.",
336 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
341 my $rpcenv = PVE
::RPCEnvironment
::get
();
342 my $authuser = $rpcenv->get_user();
344 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
347 foreach my $vmid (keys %$vmstatus) {
348 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
350 my $data = $vmstatus->{$vmid};
351 $data->{vmid
} = int($vmid);
360 __PACKAGE__-
>register_method({
364 description
=> "Create or restore a virtual machine.",
366 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
367 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
368 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
369 user
=> 'all', # check inside
374 additionalProperties
=> 0,
375 properties
=> PVE
::QemuServer
::json_config_properties
(
377 node
=> get_standard_option
('pve-node'),
378 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
380 description
=> "The backup file.",
384 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
386 storage
=> get_standard_option
('pve-storage-id', {
387 description
=> "Default storage.",
389 completion
=> \
&PVE
::QemuServer
::complete_storage
,
394 description
=> "Allow to overwrite existing VM.",
395 requires
=> 'archive',
400 description
=> "Assign a unique random ethernet address.",
401 requires
=> 'archive',
405 type
=> 'string', format
=> 'pve-poolid',
406 description
=> "Add the VM to the specified pool.",
416 my $rpcenv = PVE
::RPCEnvironment
::get
();
418 my $authuser = $rpcenv->get_user();
420 my $node = extract_param
($param, 'node');
422 my $vmid = extract_param
($param, 'vmid');
424 my $archive = extract_param
($param, 'archive');
426 my $storage = extract_param
($param, 'storage');
428 my $force = extract_param
($param, 'force');
430 my $unique = extract_param
($param, 'unique');
432 my $pool = extract_param
($param, 'pool');
434 my $filename = PVE
::QemuConfig-
>config_file($vmid);
436 my $storecfg = PVE
::Storage
::config
();
438 PVE
::Cluster
::check_cfs_quorum
();
440 if (defined($pool)) {
441 $rpcenv->check_pool_exist($pool);
444 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
445 if defined($storage);
447 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
449 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
451 } elsif ($archive && $force && (-f
$filename) &&
452 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
453 # OK: user has VM.Backup permissions, and want to restore an existing VM
459 &$resolve_cdrom_alias($param);
461 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
463 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
465 foreach my $opt (keys %$param) {
466 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
467 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
468 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
470 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
471 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
475 PVE
::QemuServer
::add_random_macs
($param);
477 my $keystr = join(' ', keys %$param);
478 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
480 if ($archive eq '-') {
481 die "pipe requires cli environment\n"
482 if $rpcenv->{type
} ne 'cli';
484 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
485 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
489 my $restorefn = sub {
490 my $vmlist = PVE
::Cluster
::get_vmlist
();
491 if ($vmlist->{ids
}->{$vmid}) {
492 my $current_node = $vmlist->{ids
}->{$vmid}->{node
};
493 if ($current_node eq $node) {
494 my $conf = PVE
::QemuConfig-
>load_config($vmid);
496 PVE
::QemuConfig-
>check_protection($conf, "unable to restore VM $vmid");
498 die "unable to restore vm $vmid - config file already exists\n"
501 die "unable to restore vm $vmid - vm is running\n"
502 if PVE
::QemuServer
::check_running
($vmid);
504 die "unable to restore vm $vmid - vm is a template\n"
505 if PVE
::QemuConfig-
>is_template($conf);
508 die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
513 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
516 unique
=> $unique });
518 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
521 # ensure no old replication state are exists
522 PVE
::ReplicationState
::delete_guest_states
($vmid);
524 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
530 PVE
::Cluster
::check_vmid_unused
($vmid);
532 # ensure no old replication state are exists
533 PVE
::ReplicationState
::delete_guest_states
($vmid);
543 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
545 if (!$conf->{bootdisk
}) {
546 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
547 $conf->{bootdisk
} = $firstdisk if $firstdisk;
550 # auto generate uuid if user did not specify smbios1 option
551 if (!$conf->{smbios1
}) {
552 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
555 PVE
::QemuConfig-
>write_config($vmid, $conf);
561 foreach my $volid (@$vollist) {
562 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
565 die "create failed - $err";
568 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
571 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
574 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $archive ?
$restorefn : $createfn);
577 __PACKAGE__-
>register_method({
582 description
=> "Directory index",
587 additionalProperties
=> 0,
589 node
=> get_standard_option
('pve-node'),
590 vmid
=> get_standard_option
('pve-vmid'),
598 subdir
=> { type
=> 'string' },
601 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
607 { subdir
=> 'config' },
608 { subdir
=> 'pending' },
609 { subdir
=> 'status' },
610 { subdir
=> 'unlink' },
611 { subdir
=> 'vncproxy' },
612 { subdir
=> 'termproxy' },
613 { subdir
=> 'migrate' },
614 { subdir
=> 'resize' },
615 { subdir
=> 'move' },
617 { subdir
=> 'rrddata' },
618 { subdir
=> 'monitor' },
619 { subdir
=> 'agent' },
620 { subdir
=> 'snapshot' },
621 { subdir
=> 'spiceproxy' },
622 { subdir
=> 'sendkey' },
623 { subdir
=> 'firewall' },
629 __PACKAGE__-
>register_method ({
630 subclass
=> "PVE::API2::Firewall::VM",
631 path
=> '{vmid}/firewall',
634 __PACKAGE__-
>register_method({
636 path
=> '{vmid}/rrd',
638 protected
=> 1, # fixme: can we avoid that?
640 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
642 description
=> "Read VM RRD statistics (returns PNG)",
644 additionalProperties
=> 0,
646 node
=> get_standard_option
('pve-node'),
647 vmid
=> get_standard_option
('pve-vmid'),
649 description
=> "Specify the time frame you are interested in.",
651 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
654 description
=> "The list of datasources you want to display.",
655 type
=> 'string', format
=> 'pve-configid-list',
658 description
=> "The RRD consolidation function",
660 enum
=> [ 'AVERAGE', 'MAX' ],
668 filename
=> { type
=> 'string' },
674 return PVE
::Cluster
::create_rrd_graph
(
675 "pve2-vm/$param->{vmid}", $param->{timeframe
},
676 $param->{ds
}, $param->{cf
});
680 __PACKAGE__-
>register_method({
682 path
=> '{vmid}/rrddata',
684 protected
=> 1, # fixme: can we avoid that?
686 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
688 description
=> "Read VM RRD statistics",
690 additionalProperties
=> 0,
692 node
=> get_standard_option
('pve-node'),
693 vmid
=> get_standard_option
('pve-vmid'),
695 description
=> "Specify the time frame you are interested in.",
697 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
700 description
=> "The RRD consolidation function",
702 enum
=> [ 'AVERAGE', 'MAX' ],
717 return PVE
::Cluster
::create_rrd_data
(
718 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
722 __PACKAGE__-
>register_method({
724 path
=> '{vmid}/config',
727 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
729 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
732 additionalProperties
=> 0,
734 node
=> get_standard_option
('pve-node'),
735 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
737 description
=> "Get current values (instead of pending values).",
749 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
756 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
758 delete $conf->{snapshots
};
760 if (!$param->{current
}) {
761 foreach my $opt (keys %{$conf->{pending
}}) {
762 next if $opt eq 'delete';
763 my $value = $conf->{pending
}->{$opt};
764 next if ref($value); # just to be sure
765 $conf->{$opt} = $value;
767 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
768 foreach my $opt (keys %$pending_delete_hash) {
769 delete $conf->{$opt} if $conf->{$opt};
773 delete $conf->{pending
};
778 __PACKAGE__-
>register_method({
779 name
=> 'vm_pending',
780 path
=> '{vmid}/pending',
783 description
=> "Get virtual machine configuration, including pending changes.",
785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
788 additionalProperties
=> 0,
790 node
=> get_standard_option
('pve-node'),
791 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
800 description
=> "Configuration option name.",
804 description
=> "Current value.",
809 description
=> "Pending value.",
814 description
=> "Indicates a pending delete request if present and not 0. " .
815 "The value 2 indicates a force-delete request.",
827 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
829 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
833 foreach my $opt (keys %$conf) {
834 next if ref($conf->{$opt});
835 my $item = { key
=> $opt };
836 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
837 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
838 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
842 foreach my $opt (keys %{$conf->{pending
}}) {
843 next if $opt eq 'delete';
844 next if ref($conf->{pending
}->{$opt}); # just to be sure
845 next if defined($conf->{$opt});
846 my $item = { key
=> $opt };
847 $item->{pending
} = $conf->{pending
}->{$opt};
851 while (my ($opt, $force) = each %$pending_delete_hash) {
852 next if $conf->{pending
}->{$opt}; # just to be sure
853 next if $conf->{$opt};
854 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
861 # POST/PUT {vmid}/config implementation
863 # The original API used PUT (idempotent) an we assumed that all operations
864 # are fast. But it turned out that almost any configuration change can
865 # involve hot-plug actions, or disk alloc/free. Such actions can take long
866 # time to complete and have side effects (not idempotent).
868 # The new implementation uses POST and forks a worker process. We added
869 # a new option 'background_delay'. If specified we wait up to
870 # 'background_delay' second for the worker task to complete. It returns null
871 # if the task is finished within that time, else we return the UPID.
873 my $update_vm_api = sub {
874 my ($param, $sync) = @_;
876 my $rpcenv = PVE
::RPCEnvironment
::get
();
878 my $authuser = $rpcenv->get_user();
880 my $node = extract_param
($param, 'node');
882 my $vmid = extract_param
($param, 'vmid');
884 my $digest = extract_param
($param, 'digest');
886 my $background_delay = extract_param
($param, 'background_delay');
888 my @paramarr = (); # used for log message
889 foreach my $key (sort keys %$param) {
890 push @paramarr, "-$key", $param->{$key};
893 my $skiplock = extract_param
($param, 'skiplock');
894 raise_param_exc
({ skiplock
=> "Only root may use this option." })
895 if $skiplock && $authuser ne 'root@pam';
897 my $delete_str = extract_param
($param, 'delete');
899 my $revert_str = extract_param
($param, 'revert');
901 my $force = extract_param
($param, 'force');
903 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
905 my $storecfg = PVE
::Storage
::config
();
907 my $defaults = PVE
::QemuServer
::load_defaults
();
909 &$resolve_cdrom_alias($param);
911 # now try to verify all parameters
914 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
915 if (!PVE
::QemuServer
::option_exists
($opt)) {
916 raise_param_exc
({ revert
=> "unknown option '$opt'" });
919 raise_param_exc
({ delete => "you can't use '-$opt' and " .
920 "-revert $opt' at the same time" })
921 if defined($param->{$opt});
927 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
928 $opt = 'ide2' if $opt eq 'cdrom';
930 raise_param_exc
({ delete => "you can't use '-$opt' and " .
931 "-delete $opt' at the same time" })
932 if defined($param->{$opt});
934 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
935 "-revert $opt' at the same time" })
938 if (!PVE
::QemuServer
::option_exists
($opt)) {
939 raise_param_exc
({ delete => "unknown option '$opt'" });
945 my $repl_conf = PVE
::ReplicationConfig-
>new();
946 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
947 my $check_replication = sub {
949 return if !$is_replicated;
950 my $volid = $drive->{file
};
951 return if !$volid || !($drive->{replicate
}//1);
952 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
953 my ($storeid, $format);
954 if ($volid =~ $NEW_DISK_RE) {
956 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
958 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
959 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
961 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
962 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
963 return if $scfg->{shared
};
964 die "cannot add non-replicatable volume to a replicated VM\n";
967 foreach my $opt (keys %$param) {
968 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
970 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
971 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
972 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
973 $check_replication->($drive);
974 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
975 } elsif ($opt =~ m/^net(\d+)$/) {
977 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
978 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
982 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
984 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
986 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
990 my $conf = PVE
::QemuConfig-
>load_config($vmid);
992 die "checksum missmatch (file change by other user?)\n"
993 if $digest && $digest ne $conf->{digest
};
995 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
997 foreach my $opt (keys %$revert) {
998 if (defined($conf->{$opt})) {
999 $param->{$opt} = $conf->{$opt};
1000 } elsif (defined($conf->{pending
}->{$opt})) {
1005 if ($param->{memory
} || defined($param->{balloon
})) {
1006 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1007 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1009 die "balloon value too large (must be smaller than assigned memory)\n"
1010 if $balloon && $balloon > $maxmem;
1013 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1017 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1019 # write updates to pending section
1021 my $modified = {}; # record what $option we modify
1023 foreach my $opt (@delete) {
1024 $modified->{$opt} = 1;
1025 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1026 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1027 warn "cannot delete '$opt' - not set in current configuration!\n";
1028 $modified->{$opt} = 0;
1032 if ($opt =~ m/^unused/) {
1033 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1034 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1035 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1036 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1037 delete $conf->{$opt};
1038 PVE
::QemuConfig-
>write_config($vmid, $conf);
1040 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1041 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1042 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1043 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1044 if defined($conf->{pending
}->{$opt});
1045 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1046 PVE
::QemuConfig-
>write_config($vmid, $conf);
1048 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1049 PVE
::QemuConfig-
>write_config($vmid, $conf);
1053 foreach my $opt (keys %$param) { # add/change
1054 $modified->{$opt} = 1;
1055 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1056 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1058 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1059 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1060 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1061 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1063 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1065 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1066 if defined($conf->{pending
}->{$opt});
1068 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1070 $conf->{pending
}->{$opt} = $param->{$opt};
1072 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1073 PVE
::QemuConfig-
>write_config($vmid, $conf);
1076 # remove pending changes when nothing changed
1077 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1078 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1079 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1081 return if !scalar(keys %{$conf->{pending
}});
1083 my $running = PVE
::QemuServer
::check_running
($vmid);
1085 # apply pending changes
1087 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1091 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1092 raise_param_exc
($errors) if scalar(keys %$errors);
1094 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1104 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1106 if ($background_delay) {
1108 # Note: It would be better to do that in the Event based HTTPServer
1109 # to avoid blocking call to sleep.
1111 my $end_time = time() + $background_delay;
1113 my $task = PVE
::Tools
::upid_decode
($upid);
1116 while (time() < $end_time) {
1117 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1119 sleep(1); # this gets interrupted when child process ends
1123 my $status = PVE
::Tools
::upid_read_status
($upid);
1124 return undef if $status eq 'OK';
1133 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1136 my $vm_config_perm_list = [
1141 'VM.Config.Network',
1143 'VM.Config.Options',
1146 __PACKAGE__-
>register_method({
1147 name
=> 'update_vm_async',
1148 path
=> '{vmid}/config',
1152 description
=> "Set virtual machine options (asynchrounous API).",
1154 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1157 additionalProperties
=> 0,
1158 properties
=> PVE
::QemuServer
::json_config_properties
(
1160 node
=> get_standard_option
('pve-node'),
1161 vmid
=> get_standard_option
('pve-vmid'),
1162 skiplock
=> get_standard_option
('skiplock'),
1164 type
=> 'string', format
=> 'pve-configid-list',
1165 description
=> "A list of settings you want to delete.",
1169 type
=> 'string', format
=> 'pve-configid-list',
1170 description
=> "Revert a pending change.",
1175 description
=> $opt_force_description,
1177 requires
=> 'delete',
1181 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1185 background_delay
=> {
1187 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1198 code
=> $update_vm_api,
1201 __PACKAGE__-
>register_method({
1202 name
=> 'update_vm',
1203 path
=> '{vmid}/config',
1207 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1209 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1212 additionalProperties
=> 0,
1213 properties
=> PVE
::QemuServer
::json_config_properties
(
1215 node
=> get_standard_option
('pve-node'),
1216 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1217 skiplock
=> get_standard_option
('skiplock'),
1219 type
=> 'string', format
=> 'pve-configid-list',
1220 description
=> "A list of settings you want to delete.",
1224 type
=> 'string', format
=> 'pve-configid-list',
1225 description
=> "Revert a pending change.",
1230 description
=> $opt_force_description,
1232 requires
=> 'delete',
1236 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1242 returns
=> { type
=> 'null' },
1245 &$update_vm_api($param, 1);
1251 __PACKAGE__-
>register_method({
1252 name
=> 'destroy_vm',
1257 description
=> "Destroy the vm (also delete all used/owned volumes).",
1259 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1262 additionalProperties
=> 0,
1264 node
=> get_standard_option
('pve-node'),
1265 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1266 skiplock
=> get_standard_option
('skiplock'),
1275 my $rpcenv = PVE
::RPCEnvironment
::get
();
1277 my $authuser = $rpcenv->get_user();
1279 my $vmid = $param->{vmid
};
1281 my $skiplock = $param->{skiplock
};
1282 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1283 if $skiplock && $authuser ne 'root@pam';
1286 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1288 my $storecfg = PVE
::Storage
::config
();
1290 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1292 die "unable to remove VM $vmid - used in HA resources\n"
1293 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1295 # do not allow destroy if there are replication jobs
1296 my $repl_conf = PVE
::ReplicationConfig-
>new();
1297 $repl_conf->check_for_existing_jobs($vmid);
1299 # early tests (repeat after locking)
1300 die "VM $vmid is running - destroy failed\n"
1301 if PVE
::QemuServer
::check_running
($vmid);
1306 syslog
('info', "destroy VM $vmid: $upid\n");
1308 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1310 PVE
::AccessControl
::remove_vm_access
($vmid);
1312 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1315 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1318 __PACKAGE__-
>register_method({
1320 path
=> '{vmid}/unlink',
1324 description
=> "Unlink/delete disk images.",
1326 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1329 additionalProperties
=> 0,
1331 node
=> get_standard_option
('pve-node'),
1332 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1334 type
=> 'string', format
=> 'pve-configid-list',
1335 description
=> "A list of disk IDs you want to delete.",
1339 description
=> $opt_force_description,
1344 returns
=> { type
=> 'null'},
1348 $param->{delete} = extract_param
($param, 'idlist');
1350 __PACKAGE__-
>update_vm($param);
1357 __PACKAGE__-
>register_method({
1359 path
=> '{vmid}/vncproxy',
1363 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1365 description
=> "Creates a TCP VNC proxy connections.",
1367 additionalProperties
=> 0,
1369 node
=> get_standard_option
('pve-node'),
1370 vmid
=> get_standard_option
('pve-vmid'),
1374 description
=> "starts websockify instead of vncproxy",
1379 additionalProperties
=> 0,
1381 user
=> { type
=> 'string' },
1382 ticket
=> { type
=> 'string' },
1383 cert
=> { type
=> 'string' },
1384 port
=> { type
=> 'integer' },
1385 upid
=> { type
=> 'string' },
1391 my $rpcenv = PVE
::RPCEnvironment
::get
();
1393 my $authuser = $rpcenv->get_user();
1395 my $vmid = $param->{vmid
};
1396 my $node = $param->{node
};
1397 my $websocket = $param->{websocket
};
1399 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1401 my $authpath = "/vms/$vmid";
1403 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1405 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1408 my ($remip, $family);
1411 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1412 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1413 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1414 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1416 $family = PVE
::Tools
::get_host_address_family
($node);
1419 my $port = PVE
::Tools
::next_vnc_port
($family);
1426 syslog
('info', "starting vnc proxy $upid\n");
1430 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1432 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1434 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1435 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1436 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1437 '-timeout', $timeout, '-authpath', $authpath,
1438 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1439 PVE
::Tools
::run_command
($cmd);
1442 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1444 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1446 my $sock = IO
::Socket
::IP-
>new(
1451 GetAddrInfoFlags
=> 0,
1452 ) or die "failed to create socket: $!\n";
1453 # Inside the worker we shouldn't have any previous alarms
1454 # running anyway...:
1456 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1458 accept(my $cli, $sock) or die "connection failed: $!\n";
1461 if (PVE
::Tools
::run_command
($cmd,
1462 output
=> '>&'.fileno($cli),
1463 input
=> '<&'.fileno($cli),
1466 die "Failed to run vncproxy.\n";
1473 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1475 PVE
::Tools
::wait_for_vnc_port
($port);
1486 __PACKAGE__-
>register_method({
1487 name
=> 'termproxy',
1488 path
=> '{vmid}/termproxy',
1492 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1494 description
=> "Creates a TCP proxy connections.",
1496 additionalProperties
=> 0,
1498 node
=> get_standard_option
('pve-node'),
1499 vmid
=> get_standard_option
('pve-vmid'),
1503 enum
=> [qw(serial0 serial1 serial2 serial3)],
1504 description
=> "opens a serial terminal (defaults to display)",
1509 additionalProperties
=> 0,
1511 user
=> { type
=> 'string' },
1512 ticket
=> { type
=> 'string' },
1513 port
=> { type
=> 'integer' },
1514 upid
=> { type
=> 'string' },
1520 my $rpcenv = PVE
::RPCEnvironment
::get
();
1522 my $authuser = $rpcenv->get_user();
1524 my $vmid = $param->{vmid
};
1525 my $node = $param->{node
};
1526 my $serial = $param->{serial
};
1528 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1530 if (!defined($serial)) {
1531 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1532 $serial = $conf->{vga
};
1536 my $authpath = "/vms/$vmid";
1538 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1540 my ($remip, $family);
1542 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1543 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1545 $family = PVE
::Tools
::get_host_address_family
($node);
1548 my $port = PVE
::Tools
::next_vnc_port
($family);
1550 my $remcmd = $remip ?
1551 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1553 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid];
1554 push @$termcmd, '-iface', $serial if $serial;
1559 syslog
('info', "starting qemu termproxy $upid\n");
1561 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1562 '--perm', 'VM.Console', '--'];
1563 push @$cmd, @$remcmd, @$termcmd;
1565 PVE
::Tools
::run_command
($cmd);
1568 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1570 PVE
::Tools
::wait_for_vnc_port
($port);
1580 __PACKAGE__-
>register_method({
1581 name
=> 'vncwebsocket',
1582 path
=> '{vmid}/vncwebsocket',
1585 description
=> "You also need to pass a valid ticket (vncticket).",
1586 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1588 description
=> "Opens a weksocket for VNC traffic.",
1590 additionalProperties
=> 0,
1592 node
=> get_standard_option
('pve-node'),
1593 vmid
=> get_standard_option
('pve-vmid'),
1595 description
=> "Ticket from previous call to vncproxy.",
1600 description
=> "Port number returned by previous vncproxy call.",
1610 port
=> { type
=> 'string' },
1616 my $rpcenv = PVE
::RPCEnvironment
::get
();
1618 my $authuser = $rpcenv->get_user();
1620 my $vmid = $param->{vmid
};
1621 my $node = $param->{node
};
1623 my $authpath = "/vms/$vmid";
1625 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1627 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1629 # Note: VNC ports are acessible from outside, so we do not gain any
1630 # security if we verify that $param->{port} belongs to VM $vmid. This
1631 # check is done by verifying the VNC ticket (inside VNC protocol).
1633 my $port = $param->{port
};
1635 return { port
=> $port };
1638 __PACKAGE__-
>register_method({
1639 name
=> 'spiceproxy',
1640 path
=> '{vmid}/spiceproxy',
1645 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1647 description
=> "Returns a SPICE configuration to connect to the VM.",
1649 additionalProperties
=> 0,
1651 node
=> get_standard_option
('pve-node'),
1652 vmid
=> get_standard_option
('pve-vmid'),
1653 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1656 returns
=> get_standard_option
('remote-viewer-config'),
1660 my $rpcenv = PVE
::RPCEnvironment
::get
();
1662 my $authuser = $rpcenv->get_user();
1664 my $vmid = $param->{vmid
};
1665 my $node = $param->{node
};
1666 my $proxy = $param->{proxy
};
1668 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1669 my $title = "VM $vmid";
1670 $title .= " - ". $conf->{name
} if $conf->{name
};
1672 my $port = PVE
::QemuServer
::spice_port
($vmid);
1674 my ($ticket, undef, $remote_viewer_config) =
1675 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1677 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1678 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1680 return $remote_viewer_config;
1683 __PACKAGE__-
>register_method({
1685 path
=> '{vmid}/status',
1688 description
=> "Directory index",
1693 additionalProperties
=> 0,
1695 node
=> get_standard_option
('pve-node'),
1696 vmid
=> get_standard_option
('pve-vmid'),
1704 subdir
=> { type
=> 'string' },
1707 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1713 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1716 { subdir
=> 'current' },
1717 { subdir
=> 'start' },
1718 { subdir
=> 'stop' },
1724 __PACKAGE__-
>register_method({
1725 name
=> 'vm_status',
1726 path
=> '{vmid}/status/current',
1729 protected
=> 1, # qemu pid files are only readable by root
1730 description
=> "Get virtual machine status.",
1732 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1735 additionalProperties
=> 0,
1737 node
=> get_standard_option
('pve-node'),
1738 vmid
=> get_standard_option
('pve-vmid'),
1741 returns
=> { type
=> 'object' },
1746 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1748 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1749 my $status = $vmstatus->{$param->{vmid
}};
1751 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1753 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1758 __PACKAGE__-
>register_method({
1760 path
=> '{vmid}/status/start',
1764 description
=> "Start virtual machine.",
1766 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1769 additionalProperties
=> 0,
1771 node
=> get_standard_option
('pve-node'),
1772 vmid
=> get_standard_option
('pve-vmid',
1773 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1774 skiplock
=> get_standard_option
('skiplock'),
1775 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1776 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1779 enum
=> ['secure', 'insecure'],
1780 description
=> "Migration traffic is encrypted using an SSH " .
1781 "tunnel by default. On secure, completely private networks " .
1782 "this can be disabled to increase performance.",
1785 migration_network
=> {
1786 type
=> 'string', format
=> 'CIDR',
1787 description
=> "CIDR of the (sub) network that is used for migration.",
1790 machine
=> get_standard_option
('pve-qm-machine'),
1792 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1804 my $rpcenv = PVE
::RPCEnvironment
::get
();
1806 my $authuser = $rpcenv->get_user();
1808 my $node = extract_param
($param, 'node');
1810 my $vmid = extract_param
($param, 'vmid');
1812 my $machine = extract_param
($param, 'machine');
1814 my $stateuri = extract_param
($param, 'stateuri');
1815 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1816 if $stateuri && $authuser ne 'root@pam';
1818 my $skiplock = extract_param
($param, 'skiplock');
1819 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1820 if $skiplock && $authuser ne 'root@pam';
1822 my $migratedfrom = extract_param
($param, 'migratedfrom');
1823 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1824 if $migratedfrom && $authuser ne 'root@pam';
1826 my $migration_type = extract_param
($param, 'migration_type');
1827 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1828 if $migration_type && $authuser ne 'root@pam';
1830 my $migration_network = extract_param
($param, 'migration_network');
1831 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1832 if $migration_network && $authuser ne 'root@pam';
1834 my $targetstorage = extract_param
($param, 'targetstorage');
1835 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1836 if $targetstorage && $authuser ne 'root@pam';
1838 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1839 if $targetstorage && !$migratedfrom;
1841 # read spice ticket from STDIN
1843 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1844 if (defined(my $line = <>)) {
1846 $spice_ticket = $line;
1850 PVE
::Cluster
::check_cfs_quorum
();
1852 my $storecfg = PVE
::Storage
::config
();
1854 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1855 $rpcenv->{type
} ne 'ha') {
1860 my $service = "vm:$vmid";
1862 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1864 print "Requesting HA start for VM $vmid\n";
1866 PVE
::Tools
::run_command
($cmd);
1871 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1878 syslog
('info', "start VM $vmid: $upid\n");
1880 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1881 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1886 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1890 __PACKAGE__-
>register_method({
1892 path
=> '{vmid}/status/stop',
1896 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1897 "is akin to pulling the power plug of a running computer and may damage the VM data",
1899 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1902 additionalProperties
=> 0,
1904 node
=> get_standard_option
('pve-node'),
1905 vmid
=> get_standard_option
('pve-vmid',
1906 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1907 skiplock
=> get_standard_option
('skiplock'),
1908 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1910 description
=> "Wait maximal timeout seconds.",
1916 description
=> "Do not deactivate storage volumes.",
1929 my $rpcenv = PVE
::RPCEnvironment
::get
();
1931 my $authuser = $rpcenv->get_user();
1933 my $node = extract_param
($param, 'node');
1935 my $vmid = extract_param
($param, 'vmid');
1937 my $skiplock = extract_param
($param, 'skiplock');
1938 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1939 if $skiplock && $authuser ne 'root@pam';
1941 my $keepActive = extract_param
($param, 'keepActive');
1942 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1943 if $keepActive && $authuser ne 'root@pam';
1945 my $migratedfrom = extract_param
($param, 'migratedfrom');
1946 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1947 if $migratedfrom && $authuser ne 'root@pam';
1950 my $storecfg = PVE
::Storage
::config
();
1952 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1957 my $service = "vm:$vmid";
1959 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1961 print "Requesting HA stop for VM $vmid\n";
1963 PVE
::Tools
::run_command
($cmd);
1968 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1974 syslog
('info', "stop VM $vmid: $upid\n");
1976 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1977 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1982 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1986 __PACKAGE__-
>register_method({
1988 path
=> '{vmid}/status/reset',
1992 description
=> "Reset virtual machine.",
1994 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1997 additionalProperties
=> 0,
1999 node
=> get_standard_option
('pve-node'),
2000 vmid
=> get_standard_option
('pve-vmid',
2001 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2002 skiplock
=> get_standard_option
('skiplock'),
2011 my $rpcenv = PVE
::RPCEnvironment
::get
();
2013 my $authuser = $rpcenv->get_user();
2015 my $node = extract_param
($param, 'node');
2017 my $vmid = extract_param
($param, 'vmid');
2019 my $skiplock = extract_param
($param, 'skiplock');
2020 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2021 if $skiplock && $authuser ne 'root@pam';
2023 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2028 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2033 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2036 __PACKAGE__-
>register_method({
2037 name
=> 'vm_shutdown',
2038 path
=> '{vmid}/status/shutdown',
2042 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2043 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2045 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2048 additionalProperties
=> 0,
2050 node
=> get_standard_option
('pve-node'),
2051 vmid
=> get_standard_option
('pve-vmid',
2052 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2053 skiplock
=> get_standard_option
('skiplock'),
2055 description
=> "Wait maximal timeout seconds.",
2061 description
=> "Make sure the VM stops.",
2067 description
=> "Do not deactivate storage volumes.",
2080 my $rpcenv = PVE
::RPCEnvironment
::get
();
2082 my $authuser = $rpcenv->get_user();
2084 my $node = extract_param
($param, 'node');
2086 my $vmid = extract_param
($param, 'vmid');
2088 my $skiplock = extract_param
($param, 'skiplock');
2089 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2090 if $skiplock && $authuser ne 'root@pam';
2092 my $keepActive = extract_param
($param, 'keepActive');
2093 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2094 if $keepActive && $authuser ne 'root@pam';
2096 my $storecfg = PVE
::Storage
::config
();
2100 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2101 # otherwise, we will infer a shutdown command, but run into the timeout,
2102 # then when the vm is resumed, it will instantly shutdown
2104 # checking the qmp status here to get feedback to the gui/cli/api
2105 # and the status query should not take too long
2108 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2112 if (!$err && $qmpstatus->{status
} eq "paused") {
2113 if ($param->{forceStop
}) {
2114 warn "VM is paused - stop instead of shutdown\n";
2117 die "VM is paused - cannot shutdown\n";
2121 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2122 ($rpcenv->{type
} ne 'ha')) {
2127 my $service = "vm:$vmid";
2129 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2131 print "Requesting HA stop for VM $vmid\n";
2133 PVE
::Tools
::run_command
($cmd);
2138 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2145 syslog
('info', "shutdown VM $vmid: $upid\n");
2147 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2148 $shutdown, $param->{forceStop
}, $keepActive);
2153 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2157 __PACKAGE__-
>register_method({
2158 name
=> 'vm_suspend',
2159 path
=> '{vmid}/status/suspend',
2163 description
=> "Suspend virtual machine.",
2165 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2168 additionalProperties
=> 0,
2170 node
=> get_standard_option
('pve-node'),
2171 vmid
=> get_standard_option
('pve-vmid',
2172 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2173 skiplock
=> get_standard_option
('skiplock'),
2182 my $rpcenv = PVE
::RPCEnvironment
::get
();
2184 my $authuser = $rpcenv->get_user();
2186 my $node = extract_param
($param, 'node');
2188 my $vmid = extract_param
($param, 'vmid');
2190 my $skiplock = extract_param
($param, 'skiplock');
2191 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2192 if $skiplock && $authuser ne 'root@pam';
2194 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2199 syslog
('info', "suspend VM $vmid: $upid\n");
2201 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2206 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2209 __PACKAGE__-
>register_method({
2210 name
=> 'vm_resume',
2211 path
=> '{vmid}/status/resume',
2215 description
=> "Resume virtual machine.",
2217 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2220 additionalProperties
=> 0,
2222 node
=> get_standard_option
('pve-node'),
2223 vmid
=> get_standard_option
('pve-vmid',
2224 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2225 skiplock
=> get_standard_option
('skiplock'),
2226 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2236 my $rpcenv = PVE
::RPCEnvironment
::get
();
2238 my $authuser = $rpcenv->get_user();
2240 my $node = extract_param
($param, 'node');
2242 my $vmid = extract_param
($param, 'vmid');
2244 my $skiplock = extract_param
($param, 'skiplock');
2245 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2246 if $skiplock && $authuser ne 'root@pam';
2248 my $nocheck = extract_param
($param, 'nocheck');
2250 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2255 syslog
('info', "resume VM $vmid: $upid\n");
2257 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2262 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2265 __PACKAGE__-
>register_method({
2266 name
=> 'vm_sendkey',
2267 path
=> '{vmid}/sendkey',
2271 description
=> "Send key event to virtual machine.",
2273 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2276 additionalProperties
=> 0,
2278 node
=> get_standard_option
('pve-node'),
2279 vmid
=> get_standard_option
('pve-vmid',
2280 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2281 skiplock
=> get_standard_option
('skiplock'),
2283 description
=> "The key (qemu monitor encoding).",
2288 returns
=> { type
=> 'null'},
2292 my $rpcenv = PVE
::RPCEnvironment
::get
();
2294 my $authuser = $rpcenv->get_user();
2296 my $node = extract_param
($param, 'node');
2298 my $vmid = extract_param
($param, 'vmid');
2300 my $skiplock = extract_param
($param, 'skiplock');
2301 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2302 if $skiplock && $authuser ne 'root@pam';
2304 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2309 __PACKAGE__-
>register_method({
2310 name
=> 'vm_feature',
2311 path
=> '{vmid}/feature',
2315 description
=> "Check if feature for virtual machine is available.",
2317 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2320 additionalProperties
=> 0,
2322 node
=> get_standard_option
('pve-node'),
2323 vmid
=> get_standard_option
('pve-vmid'),
2325 description
=> "Feature to check.",
2327 enum
=> [ 'snapshot', 'clone', 'copy' ],
2329 snapname
=> get_standard_option
('pve-snapshot-name', {
2337 hasFeature
=> { type
=> 'boolean' },
2340 items
=> { type
=> 'string' },
2347 my $node = extract_param
($param, 'node');
2349 my $vmid = extract_param
($param, 'vmid');
2351 my $snapname = extract_param
($param, 'snapname');
2353 my $feature = extract_param
($param, 'feature');
2355 my $running = PVE
::QemuServer
::check_running
($vmid);
2357 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2360 my $snap = $conf->{snapshots
}->{$snapname};
2361 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2364 my $storecfg = PVE
::Storage
::config
();
2366 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2367 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2370 hasFeature
=> $hasFeature,
2371 nodes
=> [ keys %$nodelist ],
2375 __PACKAGE__-
>register_method({
2377 path
=> '{vmid}/clone',
2381 description
=> "Create a copy of virtual machine/template.",
2383 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2384 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2385 "'Datastore.AllocateSpace' on any used storage.",
2388 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2390 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2391 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2396 additionalProperties
=> 0,
2398 node
=> get_standard_option
('pve-node'),
2399 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2400 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2403 type
=> 'string', format
=> 'dns-name',
2404 description
=> "Set a name for the new VM.",
2409 description
=> "Description for the new VM.",
2413 type
=> 'string', format
=> 'pve-poolid',
2414 description
=> "Add the new VM to the specified pool.",
2416 snapname
=> get_standard_option
('pve-snapshot-name', {
2419 storage
=> get_standard_option
('pve-storage-id', {
2420 description
=> "Target storage for full clone.",
2425 description
=> "Target format for file storage.",
2429 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2434 description
=> "Create a full copy of all disk. This is always done when " .
2435 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2438 target
=> get_standard_option
('pve-node', {
2439 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2450 my $rpcenv = PVE
::RPCEnvironment
::get
();
2452 my $authuser = $rpcenv->get_user();
2454 my $node = extract_param
($param, 'node');
2456 my $vmid = extract_param
($param, 'vmid');
2458 my $newid = extract_param
($param, 'newid');
2460 my $pool = extract_param
($param, 'pool');
2462 if (defined($pool)) {
2463 $rpcenv->check_pool_exist($pool);
2466 my $snapname = extract_param
($param, 'snapname');
2468 my $storage = extract_param
($param, 'storage');
2470 my $format = extract_param
($param, 'format');
2472 my $target = extract_param
($param, 'target');
2474 my $localnode = PVE
::INotify
::nodename
();
2476 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2478 PVE
::Cluster
::check_node_exists
($target) if $target;
2480 my $storecfg = PVE
::Storage
::config
();
2483 # check if storage is enabled on local node
2484 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2486 # check if storage is available on target node
2487 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2488 # clone only works if target storage is shared
2489 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2490 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2494 PVE
::Cluster
::check_cfs_quorum
();
2496 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2498 # exclusive lock if VM is running - else shared lock is enough;
2499 my $shared_lock = $running ?
0 : 1;
2503 # do all tests after lock
2504 # we also try to do all tests before we fork the worker
2506 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2508 PVE
::QemuConfig-
>check_lock($conf);
2510 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2512 die "unexpected state change\n" if $verify_running != $running;
2514 die "snapshot '$snapname' does not exist\n"
2515 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2517 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2519 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2521 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2523 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2525 die "unable to create VM $newid: config file already exists\n"
2528 my $newconf = { lock => 'clone' };
2533 foreach my $opt (keys %$oldconf) {
2534 my $value = $oldconf->{$opt};
2536 # do not copy snapshot related info
2537 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2538 $opt eq 'vmstate' || $opt eq 'snapstate';
2540 # no need to copy unused images, because VMID(owner) changes anyways
2541 next if $opt =~ m/^unused\d+$/;
2543 # always change MAC! address
2544 if ($opt =~ m/^net(\d+)$/) {
2545 my $net = PVE
::QemuServer
::parse_net
($value);
2546 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2547 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2548 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2549 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2550 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2551 die "unable to parse drive options for '$opt'\n" if !$drive;
2552 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2553 $newconf->{$opt} = $value; # simply copy configuration
2555 if ($param->{full
}) {
2556 die "Full clone feature is not supported for drive '$opt'\n"
2557 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2558 $fullclone->{$opt} = 1;
2560 # not full means clone instead of copy
2561 die "Linked clone feature is not supported for drive '$opt'\n"
2562 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2564 $drives->{$opt} = $drive;
2565 push @$vollist, $drive->{file
};
2568 # copy everything else
2569 $newconf->{$opt} = $value;
2573 # auto generate a new uuid
2574 my ($uuid, $uuid_str);
2575 UUID
::generate
($uuid);
2576 UUID
::unparse
($uuid, $uuid_str);
2577 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2578 $smbios1->{uuid
} = $uuid_str;
2579 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2581 delete $newconf->{template
};
2583 if ($param->{name
}) {
2584 $newconf->{name
} = $param->{name
};
2586 if ($oldconf->{name
}) {
2587 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2589 $newconf->{name
} = "Copy-of-VM-$vmid";
2593 if ($param->{description
}) {
2594 $newconf->{description
} = $param->{description
};
2597 # create empty/temp config - this fails if VM already exists on other node
2598 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2603 my $newvollist = [];
2610 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2612 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2614 my $total_jobs = scalar(keys %{$drives});
2617 foreach my $opt (keys %$drives) {
2618 my $drive = $drives->{$opt};
2619 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2621 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2622 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2623 $jobs, $skipcomplete, $oldconf->{agent
});
2625 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2627 PVE
::QemuConfig-
>write_config($newid, $newconf);
2631 delete $newconf->{lock};
2632 PVE
::QemuConfig-
>write_config($newid, $newconf);
2635 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2636 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2637 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2639 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2640 die "Failed to move config to node '$target' - rename failed: $!\n"
2641 if !rename($conffile, $newconffile);
2644 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2649 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2651 sleep 1; # some storage like rbd need to wait before release volume - really?
2653 foreach my $volid (@$newvollist) {
2654 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2657 die "clone failed: $err";
2663 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2665 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2668 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2669 # Aquire exclusive lock lock for $newid
2670 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2675 __PACKAGE__-
>register_method({
2676 name
=> 'move_vm_disk',
2677 path
=> '{vmid}/move_disk',
2681 description
=> "Move volume to different storage.",
2683 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2685 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2686 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2690 additionalProperties
=> 0,
2692 node
=> get_standard_option
('pve-node'),
2693 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2696 description
=> "The disk you want to move.",
2697 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2699 storage
=> get_standard_option
('pve-storage-id', {
2700 description
=> "Target storage.",
2701 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2705 description
=> "Target Format.",
2706 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2711 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2717 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2725 description
=> "the task ID.",
2730 my $rpcenv = PVE
::RPCEnvironment
::get
();
2732 my $authuser = $rpcenv->get_user();
2734 my $node = extract_param
($param, 'node');
2736 my $vmid = extract_param
($param, 'vmid');
2738 my $digest = extract_param
($param, 'digest');
2740 my $disk = extract_param
($param, 'disk');
2742 my $storeid = extract_param
($param, 'storage');
2744 my $format = extract_param
($param, 'format');
2746 my $storecfg = PVE
::Storage
::config
();
2748 my $updatefn = sub {
2750 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2752 PVE
::QemuConfig-
>check_lock($conf);
2754 die "checksum missmatch (file change by other user?)\n"
2755 if $digest && $digest ne $conf->{digest
};
2757 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2759 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2761 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2763 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2766 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2767 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2771 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2772 (!$format || !$oldfmt || $oldfmt eq $format);
2774 # this only checks snapshots because $disk is passed!
2775 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2776 die "you can't move a disk with snapshots and delete the source\n"
2777 if $snapshotted && $param->{delete};
2779 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2781 my $running = PVE
::QemuServer
::check_running
($vmid);
2783 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2787 my $newvollist = [];
2793 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2795 warn "moving disk with snapshots, snapshots will not be moved!\n"
2798 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2799 $vmid, $storeid, $format, 1, $newvollist);
2801 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2803 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2805 # convert moved disk to base if part of template
2806 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2807 if PVE
::QemuConfig-
>is_template($conf);
2809 PVE
::QemuConfig-
>write_config($vmid, $conf);
2812 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2813 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2820 foreach my $volid (@$newvollist) {
2821 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2824 die "storage migration failed: $err";
2827 if ($param->{delete}) {
2829 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2830 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2836 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2839 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2842 __PACKAGE__-
>register_method({
2843 name
=> 'migrate_vm',
2844 path
=> '{vmid}/migrate',
2848 description
=> "Migrate virtual machine. Creates a new migration task.",
2850 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2853 additionalProperties
=> 0,
2855 node
=> get_standard_option
('pve-node'),
2856 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2857 target
=> get_standard_option
('pve-node', {
2858 description
=> "Target node.",
2859 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2863 description
=> "Use online/live migration.",
2868 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2873 enum
=> ['secure', 'insecure'],
2874 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2877 migration_network
=> {
2878 type
=> 'string', format
=> 'CIDR',
2879 description
=> "CIDR of the (sub) network that is used for migration.",
2882 "with-local-disks" => {
2884 description
=> "Enable live storage migration for local disk",
2887 targetstorage
=> get_standard_option
('pve-storage-id', {
2888 description
=> "Default target storage.",
2890 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2896 description
=> "the task ID.",
2901 my $rpcenv = PVE
::RPCEnvironment
::get
();
2903 my $authuser = $rpcenv->get_user();
2905 my $target = extract_param
($param, 'target');
2907 my $localnode = PVE
::INotify
::nodename
();
2908 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2910 PVE
::Cluster
::check_cfs_quorum
();
2912 PVE
::Cluster
::check_node_exists
($target);
2914 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2916 my $vmid = extract_param
($param, 'vmid');
2918 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2919 if !$param->{online
} && $param->{targetstorage
};
2921 raise_param_exc
({ force
=> "Only root may use this option." })
2922 if $param->{force
} && $authuser ne 'root@pam';
2924 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2925 if $param->{migration_type
} && $authuser ne 'root@pam';
2927 # allow root only until better network permissions are available
2928 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2929 if $param->{migration_network
} && $authuser ne 'root@pam';
2932 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2934 # try to detect errors early
2936 PVE
::QemuConfig-
>check_lock($conf);
2938 if (PVE
::QemuServer
::check_running
($vmid)) {
2939 die "cant migrate running VM without --online\n"
2940 if !$param->{online
};
2943 my $storecfg = PVE
::Storage
::config
();
2945 if( $param->{targetstorage
}) {
2946 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2948 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2951 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2956 my $service = "vm:$vmid";
2958 my $cmd = ['ha-manager', 'migrate', $service, $target];
2960 print "Requesting HA migration for VM $vmid to node $target\n";
2962 PVE
::Tools
::run_command
($cmd);
2967 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2972 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2976 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2979 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2984 __PACKAGE__-
>register_method({
2986 path
=> '{vmid}/monitor',
2990 description
=> "Execute Qemu monitor commands.",
2992 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
2993 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2996 additionalProperties
=> 0,
2998 node
=> get_standard_option
('pve-node'),
2999 vmid
=> get_standard_option
('pve-vmid'),
3002 description
=> "The monitor command.",
3006 returns
=> { type
=> 'string'},
3010 my $rpcenv = PVE
::RPCEnvironment
::get
();
3011 my $authuser = $rpcenv->get_user();
3014 my $command = shift;
3015 return $command =~ m/^\s*info(\s+|$)/
3016 || $command =~ m/^\s*help\s*$/;
3019 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3020 if !&$is_ro($param->{command
});
3022 my $vmid = $param->{vmid
};
3024 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3028 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3030 $res = "ERROR: $@" if $@;
3035 my $guest_agent_commands = [
3043 'network-get-interfaces',
3046 'get-memory-blocks',
3047 'get-memory-block-info',
3054 __PACKAGE__-
>register_method({
3056 path
=> '{vmid}/agent',
3060 description
=> "Execute Qemu Guest Agent commands.",
3062 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3065 additionalProperties
=> 0,
3067 node
=> get_standard_option
('pve-node'),
3068 vmid
=> get_standard_option
('pve-vmid', {
3069 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3072 description
=> "The QGA command.",
3073 enum
=> $guest_agent_commands,
3079 description
=> "Returns an object with a single `result` property. The type of that
3080 property depends on the executed command.",
3085 my $vmid = $param->{vmid
};
3087 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3089 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3090 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3092 my $cmd = $param->{command
};
3094 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3096 return { result
=> $res };
3099 __PACKAGE__-
>register_method({
3100 name
=> 'resize_vm',
3101 path
=> '{vmid}/resize',
3105 description
=> "Extend volume size.",
3107 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3110 additionalProperties
=> 0,
3112 node
=> get_standard_option
('pve-node'),
3113 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3114 skiplock
=> get_standard_option
('skiplock'),
3117 description
=> "The disk you want to resize.",
3118 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3122 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3123 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.",
3127 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3133 returns
=> { type
=> 'null'},
3137 my $rpcenv = PVE
::RPCEnvironment
::get
();
3139 my $authuser = $rpcenv->get_user();
3141 my $node = extract_param
($param, 'node');
3143 my $vmid = extract_param
($param, 'vmid');
3145 my $digest = extract_param
($param, 'digest');
3147 my $disk = extract_param
($param, 'disk');
3149 my $sizestr = extract_param
($param, 'size');
3151 my $skiplock = extract_param
($param, 'skiplock');
3152 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3153 if $skiplock && $authuser ne 'root@pam';
3155 my $storecfg = PVE
::Storage
::config
();
3157 my $updatefn = sub {
3159 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3161 die "checksum missmatch (file change by other user?)\n"
3162 if $digest && $digest ne $conf->{digest
};
3163 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3165 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3167 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3169 my (undef, undef, undef, undef, undef, undef, $format) =
3170 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3172 die "can't resize volume: $disk if snapshot exists\n"
3173 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3175 my $volid = $drive->{file
};
3177 die "disk '$disk' has no associated volume\n" if !$volid;
3179 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3181 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3183 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3185 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3186 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3188 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3189 my ($ext, $newsize, $unit) = ($1, $2, $4);
3192 $newsize = $newsize * 1024;
3193 } elsif ($unit eq 'M') {
3194 $newsize = $newsize * 1024 * 1024;
3195 } elsif ($unit eq 'G') {
3196 $newsize = $newsize * 1024 * 1024 * 1024;
3197 } elsif ($unit eq 'T') {
3198 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3201 $newsize += $size if $ext;
3202 $newsize = int($newsize);
3204 die "shrinking disks is not supported\n" if $newsize < $size;
3206 return if $size == $newsize;
3208 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3210 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3212 $drive->{size
} = $newsize;
3213 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3215 PVE
::QemuConfig-
>write_config($vmid, $conf);
3218 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3222 __PACKAGE__-
>register_method({
3223 name
=> 'snapshot_list',
3224 path
=> '{vmid}/snapshot',
3226 description
=> "List all snapshots.",
3228 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3231 protected
=> 1, # qemu pid files are only readable by root
3233 additionalProperties
=> 0,
3235 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3236 node
=> get_standard_option
('pve-node'),
3245 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3250 my $vmid = $param->{vmid
};
3252 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3253 my $snaphash = $conf->{snapshots
} || {};
3257 foreach my $name (keys %$snaphash) {
3258 my $d = $snaphash->{$name};
3261 snaptime
=> $d->{snaptime
} || 0,
3262 vmstate
=> $d->{vmstate
} ?
1 : 0,
3263 description
=> $d->{description
} || '',
3265 $item->{parent
} = $d->{parent
} if $d->{parent
};
3266 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3270 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3271 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3272 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3274 push @$res, $current;
3279 __PACKAGE__-
>register_method({
3281 path
=> '{vmid}/snapshot',
3285 description
=> "Snapshot a VM.",
3287 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3290 additionalProperties
=> 0,
3292 node
=> get_standard_option
('pve-node'),
3293 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3294 snapname
=> get_standard_option
('pve-snapshot-name'),
3298 description
=> "Save the vmstate",
3303 description
=> "A textual description or comment.",
3309 description
=> "the task ID.",
3314 my $rpcenv = PVE
::RPCEnvironment
::get
();
3316 my $authuser = $rpcenv->get_user();
3318 my $node = extract_param
($param, 'node');
3320 my $vmid = extract_param
($param, 'vmid');
3322 my $snapname = extract_param
($param, 'snapname');
3324 die "unable to use snapshot name 'current' (reserved name)\n"
3325 if $snapname eq 'current';
3328 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3329 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3330 $param->{description
});
3333 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3336 __PACKAGE__-
>register_method({
3337 name
=> 'snapshot_cmd_idx',
3338 path
=> '{vmid}/snapshot/{snapname}',
3345 additionalProperties
=> 0,
3347 vmid
=> get_standard_option
('pve-vmid'),
3348 node
=> get_standard_option
('pve-node'),
3349 snapname
=> get_standard_option
('pve-snapshot-name'),
3358 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3365 push @$res, { cmd
=> 'rollback' };
3366 push @$res, { cmd
=> 'config' };
3371 __PACKAGE__-
>register_method({
3372 name
=> 'update_snapshot_config',
3373 path
=> '{vmid}/snapshot/{snapname}/config',
3377 description
=> "Update snapshot metadata.",
3379 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3382 additionalProperties
=> 0,
3384 node
=> get_standard_option
('pve-node'),
3385 vmid
=> get_standard_option
('pve-vmid'),
3386 snapname
=> get_standard_option
('pve-snapshot-name'),
3390 description
=> "A textual description or comment.",
3394 returns
=> { type
=> 'null' },
3398 my $rpcenv = PVE
::RPCEnvironment
::get
();
3400 my $authuser = $rpcenv->get_user();
3402 my $vmid = extract_param
($param, 'vmid');
3404 my $snapname = extract_param
($param, 'snapname');
3406 return undef if !defined($param->{description
});
3408 my $updatefn = sub {
3410 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3412 PVE
::QemuConfig-
>check_lock($conf);
3414 my $snap = $conf->{snapshots
}->{$snapname};
3416 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3418 $snap->{description
} = $param->{description
} if defined($param->{description
});
3420 PVE
::QemuConfig-
>write_config($vmid, $conf);
3423 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3428 __PACKAGE__-
>register_method({
3429 name
=> 'get_snapshot_config',
3430 path
=> '{vmid}/snapshot/{snapname}/config',
3433 description
=> "Get snapshot configuration",
3435 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3438 additionalProperties
=> 0,
3440 node
=> get_standard_option
('pve-node'),
3441 vmid
=> get_standard_option
('pve-vmid'),
3442 snapname
=> get_standard_option
('pve-snapshot-name'),
3445 returns
=> { type
=> "object" },
3449 my $rpcenv = PVE
::RPCEnvironment
::get
();
3451 my $authuser = $rpcenv->get_user();
3453 my $vmid = extract_param
($param, 'vmid');
3455 my $snapname = extract_param
($param, 'snapname');
3457 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3459 my $snap = $conf->{snapshots
}->{$snapname};
3461 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3466 __PACKAGE__-
>register_method({
3468 path
=> '{vmid}/snapshot/{snapname}/rollback',
3472 description
=> "Rollback VM state to specified snapshot.",
3474 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
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'),
3486 description
=> "the task ID.",
3491 my $rpcenv = PVE
::RPCEnvironment
::get
();
3493 my $authuser = $rpcenv->get_user();
3495 my $node = extract_param
($param, 'node');
3497 my $vmid = extract_param
($param, 'vmid');
3499 my $snapname = extract_param
($param, 'snapname');
3502 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3503 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3507 # hold migration lock, this makes sure that nobody create replication snapshots
3508 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3511 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3514 __PACKAGE__-
>register_method({
3515 name
=> 'delsnapshot',
3516 path
=> '{vmid}/snapshot/{snapname}',
3520 description
=> "Delete a VM snapshot.",
3522 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3525 additionalProperties
=> 0,
3527 node
=> get_standard_option
('pve-node'),
3528 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3529 snapname
=> get_standard_option
('pve-snapshot-name'),
3533 description
=> "For removal from config file, even if removing disk snapshots fails.",
3539 description
=> "the task ID.",
3544 my $rpcenv = PVE
::RPCEnvironment
::get
();
3546 my $authuser = $rpcenv->get_user();
3548 my $node = extract_param
($param, 'node');
3550 my $vmid = extract_param
($param, 'vmid');
3552 my $snapname = extract_param
($param, 'snapname');
3555 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3556 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3559 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3562 __PACKAGE__-
>register_method({
3564 path
=> '{vmid}/template',
3568 description
=> "Create a Template.",
3570 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3571 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3574 additionalProperties
=> 0,
3576 node
=> get_standard_option
('pve-node'),
3577 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3581 description
=> "If you want to convert only 1 disk to base image.",
3582 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3587 returns
=> { type
=> 'null'},
3591 my $rpcenv = PVE
::RPCEnvironment
::get
();
3593 my $authuser = $rpcenv->get_user();
3595 my $node = extract_param
($param, 'node');
3597 my $vmid = extract_param
($param, 'vmid');
3599 my $disk = extract_param
($param, 'disk');
3601 my $updatefn = sub {
3603 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3605 PVE
::QemuConfig-
>check_lock($conf);
3607 die "unable to create template, because VM contains snapshots\n"
3608 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3610 die "you can't convert a template to a template\n"
3611 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3613 die "you can't convert a VM to template if VM is running\n"
3614 if PVE
::QemuServer
::check_running
($vmid);
3617 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3620 $conf->{template
} = 1;
3621 PVE
::QemuConfig-
>write_config($vmid, $conf);
3623 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3626 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);