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+$/)) {
1433 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1435 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1436 '-timeout', $timeout, '-authpath', $authpath,
1437 '-perm', 'Sys.Console'];
1439 if ($param->{websocket
}) {
1440 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1441 push @$cmd, '-notls', '-listen', 'localhost';
1444 push @$cmd, '-c', @$remcmd, @$termcmd;
1446 PVE
::Tools
::run_command
($cmd);
1450 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1452 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1454 my $sock = IO
::Socket
::IP-
>new(
1459 GetAddrInfoFlags
=> 0,
1460 ) or die "failed to create socket: $!\n";
1461 # Inside the worker we shouldn't have any previous alarms
1462 # running anyway...:
1464 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1466 accept(my $cli, $sock) or die "connection failed: $!\n";
1469 if (PVE
::Tools
::run_command
($cmd,
1470 output
=> '>&'.fileno($cli),
1471 input
=> '<&'.fileno($cli),
1474 die "Failed to run vncproxy.\n";
1481 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1483 PVE
::Tools
::wait_for_vnc_port
($port);
1494 __PACKAGE__-
>register_method({
1495 name
=> 'termproxy',
1496 path
=> '{vmid}/termproxy',
1500 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1502 description
=> "Creates a TCP proxy connections.",
1504 additionalProperties
=> 0,
1506 node
=> get_standard_option
('pve-node'),
1507 vmid
=> get_standard_option
('pve-vmid'),
1511 enum
=> [qw(serial0 serial1 serial2 serial3)],
1512 description
=> "opens a serial terminal (defaults to display)",
1517 additionalProperties
=> 0,
1519 user
=> { type
=> 'string' },
1520 ticket
=> { type
=> 'string' },
1521 port
=> { type
=> 'integer' },
1522 upid
=> { type
=> 'string' },
1528 my $rpcenv = PVE
::RPCEnvironment
::get
();
1530 my $authuser = $rpcenv->get_user();
1532 my $vmid = $param->{vmid
};
1533 my $node = $param->{node
};
1534 my $serial = $param->{serial
};
1536 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1538 if (!defined($serial)) {
1539 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1540 $serial = $conf->{vga
};
1544 my $authpath = "/vms/$vmid";
1546 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1548 my ($remip, $family);
1550 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1551 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1553 $family = PVE
::Tools
::get_host_address_family
($node);
1556 my $port = PVE
::Tools
::next_vnc_port
($family);
1558 my $remcmd = $remip ?
1559 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1561 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid];
1562 push @$termcmd, '-iface', $serial if $serial;
1567 syslog
('info', "starting qemu termproxy $upid\n");
1569 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1570 '--perm', 'VM.Console', '--'];
1571 push @$cmd, @$remcmd, @$termcmd;
1573 PVE
::Tools
::run_command
($cmd);
1576 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1578 PVE
::Tools
::wait_for_vnc_port
($port);
1588 __PACKAGE__-
>register_method({
1589 name
=> 'vncwebsocket',
1590 path
=> '{vmid}/vncwebsocket',
1593 description
=> "You also need to pass a valid ticket (vncticket).",
1594 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1596 description
=> "Opens a weksocket for VNC traffic.",
1598 additionalProperties
=> 0,
1600 node
=> get_standard_option
('pve-node'),
1601 vmid
=> get_standard_option
('pve-vmid'),
1603 description
=> "Ticket from previous call to vncproxy.",
1608 description
=> "Port number returned by previous vncproxy call.",
1618 port
=> { type
=> 'string' },
1624 my $rpcenv = PVE
::RPCEnvironment
::get
();
1626 my $authuser = $rpcenv->get_user();
1628 my $vmid = $param->{vmid
};
1629 my $node = $param->{node
};
1631 my $authpath = "/vms/$vmid";
1633 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1635 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1637 # Note: VNC ports are acessible from outside, so we do not gain any
1638 # security if we verify that $param->{port} belongs to VM $vmid. This
1639 # check is done by verifying the VNC ticket (inside VNC protocol).
1641 my $port = $param->{port
};
1643 return { port
=> $port };
1646 __PACKAGE__-
>register_method({
1647 name
=> 'spiceproxy',
1648 path
=> '{vmid}/spiceproxy',
1653 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1655 description
=> "Returns a SPICE configuration to connect to the VM.",
1657 additionalProperties
=> 0,
1659 node
=> get_standard_option
('pve-node'),
1660 vmid
=> get_standard_option
('pve-vmid'),
1661 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1664 returns
=> get_standard_option
('remote-viewer-config'),
1668 my $rpcenv = PVE
::RPCEnvironment
::get
();
1670 my $authuser = $rpcenv->get_user();
1672 my $vmid = $param->{vmid
};
1673 my $node = $param->{node
};
1674 my $proxy = $param->{proxy
};
1676 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1677 my $title = "VM $vmid";
1678 $title .= " - ". $conf->{name
} if $conf->{name
};
1680 my $port = PVE
::QemuServer
::spice_port
($vmid);
1682 my ($ticket, undef, $remote_viewer_config) =
1683 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1685 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1686 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1688 return $remote_viewer_config;
1691 __PACKAGE__-
>register_method({
1693 path
=> '{vmid}/status',
1696 description
=> "Directory index",
1701 additionalProperties
=> 0,
1703 node
=> get_standard_option
('pve-node'),
1704 vmid
=> get_standard_option
('pve-vmid'),
1712 subdir
=> { type
=> 'string' },
1715 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1721 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1724 { subdir
=> 'current' },
1725 { subdir
=> 'start' },
1726 { subdir
=> 'stop' },
1732 __PACKAGE__-
>register_method({
1733 name
=> 'vm_status',
1734 path
=> '{vmid}/status/current',
1737 protected
=> 1, # qemu pid files are only readable by root
1738 description
=> "Get virtual machine status.",
1740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1743 additionalProperties
=> 0,
1745 node
=> get_standard_option
('pve-node'),
1746 vmid
=> get_standard_option
('pve-vmid'),
1749 returns
=> { type
=> 'object' },
1754 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1756 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1757 my $status = $vmstatus->{$param->{vmid
}};
1759 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1761 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1766 __PACKAGE__-
>register_method({
1768 path
=> '{vmid}/status/start',
1772 description
=> "Start virtual machine.",
1774 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1777 additionalProperties
=> 0,
1779 node
=> get_standard_option
('pve-node'),
1780 vmid
=> get_standard_option
('pve-vmid',
1781 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1782 skiplock
=> get_standard_option
('skiplock'),
1783 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1784 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1787 enum
=> ['secure', 'insecure'],
1788 description
=> "Migration traffic is encrypted using an SSH " .
1789 "tunnel by default. On secure, completely private networks " .
1790 "this can be disabled to increase performance.",
1793 migration_network
=> {
1794 type
=> 'string', format
=> 'CIDR',
1795 description
=> "CIDR of the (sub) network that is used for migration.",
1798 machine
=> get_standard_option
('pve-qm-machine'),
1800 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1812 my $rpcenv = PVE
::RPCEnvironment
::get
();
1814 my $authuser = $rpcenv->get_user();
1816 my $node = extract_param
($param, 'node');
1818 my $vmid = extract_param
($param, 'vmid');
1820 my $machine = extract_param
($param, 'machine');
1822 my $stateuri = extract_param
($param, 'stateuri');
1823 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1824 if $stateuri && $authuser ne 'root@pam';
1826 my $skiplock = extract_param
($param, 'skiplock');
1827 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1828 if $skiplock && $authuser ne 'root@pam';
1830 my $migratedfrom = extract_param
($param, 'migratedfrom');
1831 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1832 if $migratedfrom && $authuser ne 'root@pam';
1834 my $migration_type = extract_param
($param, 'migration_type');
1835 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1836 if $migration_type && $authuser ne 'root@pam';
1838 my $migration_network = extract_param
($param, 'migration_network');
1839 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1840 if $migration_network && $authuser ne 'root@pam';
1842 my $targetstorage = extract_param
($param, 'targetstorage');
1843 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1844 if $targetstorage && $authuser ne 'root@pam';
1846 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1847 if $targetstorage && !$migratedfrom;
1849 # read spice ticket from STDIN
1851 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1852 if (defined(my $line = <>)) {
1854 $spice_ticket = $line;
1858 PVE
::Cluster
::check_cfs_quorum
();
1860 my $storecfg = PVE
::Storage
::config
();
1862 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1863 $rpcenv->{type
} ne 'ha') {
1868 my $service = "vm:$vmid";
1870 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1872 print "Requesting HA start for VM $vmid\n";
1874 PVE
::Tools
::run_command
($cmd);
1879 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1886 syslog
('info', "start VM $vmid: $upid\n");
1888 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1889 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1894 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1898 __PACKAGE__-
>register_method({
1900 path
=> '{vmid}/status/stop',
1904 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1905 "is akin to pulling the power plug of a running computer and may damage the VM data",
1907 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1910 additionalProperties
=> 0,
1912 node
=> get_standard_option
('pve-node'),
1913 vmid
=> get_standard_option
('pve-vmid',
1914 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1915 skiplock
=> get_standard_option
('skiplock'),
1916 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1918 description
=> "Wait maximal timeout seconds.",
1924 description
=> "Do not deactivate storage volumes.",
1937 my $rpcenv = PVE
::RPCEnvironment
::get
();
1939 my $authuser = $rpcenv->get_user();
1941 my $node = extract_param
($param, 'node');
1943 my $vmid = extract_param
($param, 'vmid');
1945 my $skiplock = extract_param
($param, 'skiplock');
1946 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1947 if $skiplock && $authuser ne 'root@pam';
1949 my $keepActive = extract_param
($param, 'keepActive');
1950 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1951 if $keepActive && $authuser ne 'root@pam';
1953 my $migratedfrom = extract_param
($param, 'migratedfrom');
1954 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1955 if $migratedfrom && $authuser ne 'root@pam';
1958 my $storecfg = PVE
::Storage
::config
();
1960 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1965 my $service = "vm:$vmid";
1967 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1969 print "Requesting HA stop for VM $vmid\n";
1971 PVE
::Tools
::run_command
($cmd);
1976 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1982 syslog
('info', "stop VM $vmid: $upid\n");
1984 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1985 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1990 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1994 __PACKAGE__-
>register_method({
1996 path
=> '{vmid}/status/reset',
2000 description
=> "Reset virtual machine.",
2002 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2005 additionalProperties
=> 0,
2007 node
=> get_standard_option
('pve-node'),
2008 vmid
=> get_standard_option
('pve-vmid',
2009 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2010 skiplock
=> get_standard_option
('skiplock'),
2019 my $rpcenv = PVE
::RPCEnvironment
::get
();
2021 my $authuser = $rpcenv->get_user();
2023 my $node = extract_param
($param, 'node');
2025 my $vmid = extract_param
($param, 'vmid');
2027 my $skiplock = extract_param
($param, 'skiplock');
2028 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2029 if $skiplock && $authuser ne 'root@pam';
2031 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2036 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2041 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2044 __PACKAGE__-
>register_method({
2045 name
=> 'vm_shutdown',
2046 path
=> '{vmid}/status/shutdown',
2050 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2051 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2053 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2056 additionalProperties
=> 0,
2058 node
=> get_standard_option
('pve-node'),
2059 vmid
=> get_standard_option
('pve-vmid',
2060 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2061 skiplock
=> get_standard_option
('skiplock'),
2063 description
=> "Wait maximal timeout seconds.",
2069 description
=> "Make sure the VM stops.",
2075 description
=> "Do not deactivate storage volumes.",
2088 my $rpcenv = PVE
::RPCEnvironment
::get
();
2090 my $authuser = $rpcenv->get_user();
2092 my $node = extract_param
($param, 'node');
2094 my $vmid = extract_param
($param, 'vmid');
2096 my $skiplock = extract_param
($param, 'skiplock');
2097 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2098 if $skiplock && $authuser ne 'root@pam';
2100 my $keepActive = extract_param
($param, 'keepActive');
2101 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2102 if $keepActive && $authuser ne 'root@pam';
2104 my $storecfg = PVE
::Storage
::config
();
2108 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2109 # otherwise, we will infer a shutdown command, but run into the timeout,
2110 # then when the vm is resumed, it will instantly shutdown
2112 # checking the qmp status here to get feedback to the gui/cli/api
2113 # and the status query should not take too long
2116 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2120 if (!$err && $qmpstatus->{status
} eq "paused") {
2121 if ($param->{forceStop
}) {
2122 warn "VM is paused - stop instead of shutdown\n";
2125 die "VM is paused - cannot shutdown\n";
2129 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2130 ($rpcenv->{type
} ne 'ha')) {
2135 my $service = "vm:$vmid";
2137 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2139 print "Requesting HA stop for VM $vmid\n";
2141 PVE
::Tools
::run_command
($cmd);
2146 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2153 syslog
('info', "shutdown VM $vmid: $upid\n");
2155 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2156 $shutdown, $param->{forceStop
}, $keepActive);
2161 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2165 __PACKAGE__-
>register_method({
2166 name
=> 'vm_suspend',
2167 path
=> '{vmid}/status/suspend',
2171 description
=> "Suspend virtual machine.",
2173 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2176 additionalProperties
=> 0,
2178 node
=> get_standard_option
('pve-node'),
2179 vmid
=> get_standard_option
('pve-vmid',
2180 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2181 skiplock
=> get_standard_option
('skiplock'),
2190 my $rpcenv = PVE
::RPCEnvironment
::get
();
2192 my $authuser = $rpcenv->get_user();
2194 my $node = extract_param
($param, 'node');
2196 my $vmid = extract_param
($param, 'vmid');
2198 my $skiplock = extract_param
($param, 'skiplock');
2199 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2200 if $skiplock && $authuser ne 'root@pam';
2202 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2207 syslog
('info', "suspend VM $vmid: $upid\n");
2209 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2214 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2217 __PACKAGE__-
>register_method({
2218 name
=> 'vm_resume',
2219 path
=> '{vmid}/status/resume',
2223 description
=> "Resume virtual machine.",
2225 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2228 additionalProperties
=> 0,
2230 node
=> get_standard_option
('pve-node'),
2231 vmid
=> get_standard_option
('pve-vmid',
2232 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2233 skiplock
=> get_standard_option
('skiplock'),
2234 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2244 my $rpcenv = PVE
::RPCEnvironment
::get
();
2246 my $authuser = $rpcenv->get_user();
2248 my $node = extract_param
($param, 'node');
2250 my $vmid = extract_param
($param, 'vmid');
2252 my $skiplock = extract_param
($param, 'skiplock');
2253 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2254 if $skiplock && $authuser ne 'root@pam';
2256 my $nocheck = extract_param
($param, 'nocheck');
2258 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2263 syslog
('info', "resume VM $vmid: $upid\n");
2265 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2270 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2273 __PACKAGE__-
>register_method({
2274 name
=> 'vm_sendkey',
2275 path
=> '{vmid}/sendkey',
2279 description
=> "Send key event to virtual machine.",
2281 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2284 additionalProperties
=> 0,
2286 node
=> get_standard_option
('pve-node'),
2287 vmid
=> get_standard_option
('pve-vmid',
2288 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2289 skiplock
=> get_standard_option
('skiplock'),
2291 description
=> "The key (qemu monitor encoding).",
2296 returns
=> { type
=> 'null'},
2300 my $rpcenv = PVE
::RPCEnvironment
::get
();
2302 my $authuser = $rpcenv->get_user();
2304 my $node = extract_param
($param, 'node');
2306 my $vmid = extract_param
($param, 'vmid');
2308 my $skiplock = extract_param
($param, 'skiplock');
2309 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2310 if $skiplock && $authuser ne 'root@pam';
2312 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2317 __PACKAGE__-
>register_method({
2318 name
=> 'vm_feature',
2319 path
=> '{vmid}/feature',
2323 description
=> "Check if feature for virtual machine is available.",
2325 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2328 additionalProperties
=> 0,
2330 node
=> get_standard_option
('pve-node'),
2331 vmid
=> get_standard_option
('pve-vmid'),
2333 description
=> "Feature to check.",
2335 enum
=> [ 'snapshot', 'clone', 'copy' ],
2337 snapname
=> get_standard_option
('pve-snapshot-name', {
2345 hasFeature
=> { type
=> 'boolean' },
2348 items
=> { type
=> 'string' },
2355 my $node = extract_param
($param, 'node');
2357 my $vmid = extract_param
($param, 'vmid');
2359 my $snapname = extract_param
($param, 'snapname');
2361 my $feature = extract_param
($param, 'feature');
2363 my $running = PVE
::QemuServer
::check_running
($vmid);
2365 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2368 my $snap = $conf->{snapshots
}->{$snapname};
2369 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2372 my $storecfg = PVE
::Storage
::config
();
2374 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2375 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2378 hasFeature
=> $hasFeature,
2379 nodes
=> [ keys %$nodelist ],
2383 __PACKAGE__-
>register_method({
2385 path
=> '{vmid}/clone',
2389 description
=> "Create a copy of virtual machine/template.",
2391 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2392 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2393 "'Datastore.AllocateSpace' on any used storage.",
2396 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2398 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2399 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2404 additionalProperties
=> 0,
2406 node
=> get_standard_option
('pve-node'),
2407 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2408 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2411 type
=> 'string', format
=> 'dns-name',
2412 description
=> "Set a name for the new VM.",
2417 description
=> "Description for the new VM.",
2421 type
=> 'string', format
=> 'pve-poolid',
2422 description
=> "Add the new VM to the specified pool.",
2424 snapname
=> get_standard_option
('pve-snapshot-name', {
2427 storage
=> get_standard_option
('pve-storage-id', {
2428 description
=> "Target storage for full clone.",
2433 description
=> "Target format for file storage.",
2437 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2442 description
=> "Create a full copy of all disk. This is always done when " .
2443 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2446 target
=> get_standard_option
('pve-node', {
2447 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2458 my $rpcenv = PVE
::RPCEnvironment
::get
();
2460 my $authuser = $rpcenv->get_user();
2462 my $node = extract_param
($param, 'node');
2464 my $vmid = extract_param
($param, 'vmid');
2466 my $newid = extract_param
($param, 'newid');
2468 my $pool = extract_param
($param, 'pool');
2470 if (defined($pool)) {
2471 $rpcenv->check_pool_exist($pool);
2474 my $snapname = extract_param
($param, 'snapname');
2476 my $storage = extract_param
($param, 'storage');
2478 my $format = extract_param
($param, 'format');
2480 my $target = extract_param
($param, 'target');
2482 my $localnode = PVE
::INotify
::nodename
();
2484 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2486 PVE
::Cluster
::check_node_exists
($target) if $target;
2488 my $storecfg = PVE
::Storage
::config
();
2491 # check if storage is enabled on local node
2492 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2494 # check if storage is available on target node
2495 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2496 # clone only works if target storage is shared
2497 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2498 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2502 PVE
::Cluster
::check_cfs_quorum
();
2504 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2506 # exclusive lock if VM is running - else shared lock is enough;
2507 my $shared_lock = $running ?
0 : 1;
2511 # do all tests after lock
2512 # we also try to do all tests before we fork the worker
2514 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2516 PVE
::QemuConfig-
>check_lock($conf);
2518 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2520 die "unexpected state change\n" if $verify_running != $running;
2522 die "snapshot '$snapname' does not exist\n"
2523 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2525 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2527 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2529 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2531 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2533 die "unable to create VM $newid: config file already exists\n"
2536 my $newconf = { lock => 'clone' };
2541 foreach my $opt (keys %$oldconf) {
2542 my $value = $oldconf->{$opt};
2544 # do not copy snapshot related info
2545 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2546 $opt eq 'vmstate' || $opt eq 'snapstate';
2548 # no need to copy unused images, because VMID(owner) changes anyways
2549 next if $opt =~ m/^unused\d+$/;
2551 # always change MAC! address
2552 if ($opt =~ m/^net(\d+)$/) {
2553 my $net = PVE
::QemuServer
::parse_net
($value);
2554 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2555 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2556 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2557 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2558 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2559 die "unable to parse drive options for '$opt'\n" if !$drive;
2560 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2561 $newconf->{$opt} = $value; # simply copy configuration
2563 if ($param->{full
}) {
2564 die "Full clone feature is not supported for drive '$opt'\n"
2565 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2566 $fullclone->{$opt} = 1;
2568 # not full means clone instead of copy
2569 die "Linked clone feature is not supported for drive '$opt'\n"
2570 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2572 $drives->{$opt} = $drive;
2573 push @$vollist, $drive->{file
};
2576 # copy everything else
2577 $newconf->{$opt} = $value;
2581 # auto generate a new uuid
2582 my ($uuid, $uuid_str);
2583 UUID
::generate
($uuid);
2584 UUID
::unparse
($uuid, $uuid_str);
2585 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2586 $smbios1->{uuid
} = $uuid_str;
2587 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2589 delete $newconf->{template
};
2591 if ($param->{name
}) {
2592 $newconf->{name
} = $param->{name
};
2594 if ($oldconf->{name
}) {
2595 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2597 $newconf->{name
} = "Copy-of-VM-$vmid";
2601 if ($param->{description
}) {
2602 $newconf->{description
} = $param->{description
};
2605 # create empty/temp config - this fails if VM already exists on other node
2606 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2611 my $newvollist = [];
2618 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2620 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2622 my $total_jobs = scalar(keys %{$drives});
2625 foreach my $opt (keys %$drives) {
2626 my $drive = $drives->{$opt};
2627 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2629 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2630 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2631 $jobs, $skipcomplete, $oldconf->{agent
});
2633 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2635 PVE
::QemuConfig-
>write_config($newid, $newconf);
2639 delete $newconf->{lock};
2640 PVE
::QemuConfig-
>write_config($newid, $newconf);
2643 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2644 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2645 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2647 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2648 die "Failed to move config to node '$target' - rename failed: $!\n"
2649 if !rename($conffile, $newconffile);
2652 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2657 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2659 sleep 1; # some storage like rbd need to wait before release volume - really?
2661 foreach my $volid (@$newvollist) {
2662 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2665 die "clone failed: $err";
2671 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2673 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2676 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2677 # Aquire exclusive lock lock for $newid
2678 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2683 __PACKAGE__-
>register_method({
2684 name
=> 'move_vm_disk',
2685 path
=> '{vmid}/move_disk',
2689 description
=> "Move volume to different storage.",
2691 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2693 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2694 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2698 additionalProperties
=> 0,
2700 node
=> get_standard_option
('pve-node'),
2701 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2704 description
=> "The disk you want to move.",
2705 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2707 storage
=> get_standard_option
('pve-storage-id', {
2708 description
=> "Target storage.",
2709 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2713 description
=> "Target Format.",
2714 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2719 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2725 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2733 description
=> "the task ID.",
2738 my $rpcenv = PVE
::RPCEnvironment
::get
();
2740 my $authuser = $rpcenv->get_user();
2742 my $node = extract_param
($param, 'node');
2744 my $vmid = extract_param
($param, 'vmid');
2746 my $digest = extract_param
($param, 'digest');
2748 my $disk = extract_param
($param, 'disk');
2750 my $storeid = extract_param
($param, 'storage');
2752 my $format = extract_param
($param, 'format');
2754 my $storecfg = PVE
::Storage
::config
();
2756 my $updatefn = sub {
2758 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2760 PVE
::QemuConfig-
>check_lock($conf);
2762 die "checksum missmatch (file change by other user?)\n"
2763 if $digest && $digest ne $conf->{digest
};
2765 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2767 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2769 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2771 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2774 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2775 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2779 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2780 (!$format || !$oldfmt || $oldfmt eq $format);
2782 # this only checks snapshots because $disk is passed!
2783 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2784 die "you can't move a disk with snapshots and delete the source\n"
2785 if $snapshotted && $param->{delete};
2787 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2789 my $running = PVE
::QemuServer
::check_running
($vmid);
2791 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2795 my $newvollist = [];
2801 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2803 warn "moving disk with snapshots, snapshots will not be moved!\n"
2806 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2807 $vmid, $storeid, $format, 1, $newvollist);
2809 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2811 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2813 # convert moved disk to base if part of template
2814 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2815 if PVE
::QemuConfig-
>is_template($conf);
2817 PVE
::QemuConfig-
>write_config($vmid, $conf);
2820 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2821 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2828 foreach my $volid (@$newvollist) {
2829 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2832 die "storage migration failed: $err";
2835 if ($param->{delete}) {
2837 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2838 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2844 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2847 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2850 __PACKAGE__-
>register_method({
2851 name
=> 'migrate_vm',
2852 path
=> '{vmid}/migrate',
2856 description
=> "Migrate virtual machine. Creates a new migration task.",
2858 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2861 additionalProperties
=> 0,
2863 node
=> get_standard_option
('pve-node'),
2864 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2865 target
=> get_standard_option
('pve-node', {
2866 description
=> "Target node.",
2867 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2871 description
=> "Use online/live migration.",
2876 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2881 enum
=> ['secure', 'insecure'],
2882 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2885 migration_network
=> {
2886 type
=> 'string', format
=> 'CIDR',
2887 description
=> "CIDR of the (sub) network that is used for migration.",
2890 "with-local-disks" => {
2892 description
=> "Enable live storage migration for local disk",
2895 targetstorage
=> get_standard_option
('pve-storage-id', {
2896 description
=> "Default target storage.",
2898 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2904 description
=> "the task ID.",
2909 my $rpcenv = PVE
::RPCEnvironment
::get
();
2911 my $authuser = $rpcenv->get_user();
2913 my $target = extract_param
($param, 'target');
2915 my $localnode = PVE
::INotify
::nodename
();
2916 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2918 PVE
::Cluster
::check_cfs_quorum
();
2920 PVE
::Cluster
::check_node_exists
($target);
2922 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2924 my $vmid = extract_param
($param, 'vmid');
2926 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2927 if !$param->{online
} && $param->{targetstorage
};
2929 raise_param_exc
({ force
=> "Only root may use this option." })
2930 if $param->{force
} && $authuser ne 'root@pam';
2932 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2933 if $param->{migration_type
} && $authuser ne 'root@pam';
2935 # allow root only until better network permissions are available
2936 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2937 if $param->{migration_network
} && $authuser ne 'root@pam';
2940 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2942 # try to detect errors early
2944 PVE
::QemuConfig-
>check_lock($conf);
2946 if (PVE
::QemuServer
::check_running
($vmid)) {
2947 die "cant migrate running VM without --online\n"
2948 if !$param->{online
};
2951 my $storecfg = PVE
::Storage
::config
();
2953 if( $param->{targetstorage
}) {
2954 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2956 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2959 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2964 my $service = "vm:$vmid";
2966 my $cmd = ['ha-manager', 'migrate', $service, $target];
2968 print "Requesting HA migration for VM $vmid to node $target\n";
2970 PVE
::Tools
::run_command
($cmd);
2975 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2980 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2984 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2987 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2992 __PACKAGE__-
>register_method({
2994 path
=> '{vmid}/monitor',
2998 description
=> "Execute Qemu monitor commands.",
3000 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3001 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3004 additionalProperties
=> 0,
3006 node
=> get_standard_option
('pve-node'),
3007 vmid
=> get_standard_option
('pve-vmid'),
3010 description
=> "The monitor command.",
3014 returns
=> { type
=> 'string'},
3018 my $rpcenv = PVE
::RPCEnvironment
::get
();
3019 my $authuser = $rpcenv->get_user();
3022 my $command = shift;
3023 return $command =~ m/^\s*info(\s+|$)/
3024 || $command =~ m/^\s*help\s*$/;
3027 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3028 if !&$is_ro($param->{command
});
3030 my $vmid = $param->{vmid
};
3032 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3036 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3038 $res = "ERROR: $@" if $@;
3043 my $guest_agent_commands = [
3051 'network-get-interfaces',
3054 'get-memory-blocks',
3055 'get-memory-block-info',
3062 __PACKAGE__-
>register_method({
3064 path
=> '{vmid}/agent',
3068 description
=> "Execute Qemu Guest Agent commands.",
3070 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3073 additionalProperties
=> 0,
3075 node
=> get_standard_option
('pve-node'),
3076 vmid
=> get_standard_option
('pve-vmid', {
3077 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3080 description
=> "The QGA command.",
3081 enum
=> $guest_agent_commands,
3087 description
=> "Returns an object with a single `result` property. The type of that
3088 property depends on the executed command.",
3093 my $vmid = $param->{vmid
};
3095 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3097 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3098 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3100 my $cmd = $param->{command
};
3102 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3104 return { result
=> $res };
3107 __PACKAGE__-
>register_method({
3108 name
=> 'resize_vm',
3109 path
=> '{vmid}/resize',
3113 description
=> "Extend volume size.",
3115 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3118 additionalProperties
=> 0,
3120 node
=> get_standard_option
('pve-node'),
3121 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3122 skiplock
=> get_standard_option
('skiplock'),
3125 description
=> "The disk you want to resize.",
3126 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3130 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3131 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.",
3135 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3141 returns
=> { type
=> 'null'},
3145 my $rpcenv = PVE
::RPCEnvironment
::get
();
3147 my $authuser = $rpcenv->get_user();
3149 my $node = extract_param
($param, 'node');
3151 my $vmid = extract_param
($param, 'vmid');
3153 my $digest = extract_param
($param, 'digest');
3155 my $disk = extract_param
($param, 'disk');
3157 my $sizestr = extract_param
($param, 'size');
3159 my $skiplock = extract_param
($param, 'skiplock');
3160 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3161 if $skiplock && $authuser ne 'root@pam';
3163 my $storecfg = PVE
::Storage
::config
();
3165 my $updatefn = sub {
3167 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3169 die "checksum missmatch (file change by other user?)\n"
3170 if $digest && $digest ne $conf->{digest
};
3171 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3173 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3175 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3177 my (undef, undef, undef, undef, undef, undef, $format) =
3178 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3180 die "can't resize volume: $disk if snapshot exists\n"
3181 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3183 my $volid = $drive->{file
};
3185 die "disk '$disk' has no associated volume\n" if !$volid;
3187 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3189 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3191 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3193 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3194 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3196 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3197 my ($ext, $newsize, $unit) = ($1, $2, $4);
3200 $newsize = $newsize * 1024;
3201 } elsif ($unit eq 'M') {
3202 $newsize = $newsize * 1024 * 1024;
3203 } elsif ($unit eq 'G') {
3204 $newsize = $newsize * 1024 * 1024 * 1024;
3205 } elsif ($unit eq 'T') {
3206 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3209 $newsize += $size if $ext;
3210 $newsize = int($newsize);
3212 die "shrinking disks is not supported\n" if $newsize < $size;
3214 return if $size == $newsize;
3216 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3218 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3220 $drive->{size
} = $newsize;
3221 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3223 PVE
::QemuConfig-
>write_config($vmid, $conf);
3226 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3230 __PACKAGE__-
>register_method({
3231 name
=> 'snapshot_list',
3232 path
=> '{vmid}/snapshot',
3234 description
=> "List all snapshots.",
3236 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3239 protected
=> 1, # qemu pid files are only readable by root
3241 additionalProperties
=> 0,
3243 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3244 node
=> get_standard_option
('pve-node'),
3253 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3258 my $vmid = $param->{vmid
};
3260 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3261 my $snaphash = $conf->{snapshots
} || {};
3265 foreach my $name (keys %$snaphash) {
3266 my $d = $snaphash->{$name};
3269 snaptime
=> $d->{snaptime
} || 0,
3270 vmstate
=> $d->{vmstate
} ?
1 : 0,
3271 description
=> $d->{description
} || '',
3273 $item->{parent
} = $d->{parent
} if $d->{parent
};
3274 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3278 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3279 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3280 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3282 push @$res, $current;
3287 __PACKAGE__-
>register_method({
3289 path
=> '{vmid}/snapshot',
3293 description
=> "Snapshot a VM.",
3295 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3298 additionalProperties
=> 0,
3300 node
=> get_standard_option
('pve-node'),
3301 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3302 snapname
=> get_standard_option
('pve-snapshot-name'),
3306 description
=> "Save the vmstate",
3311 description
=> "A textual description or comment.",
3317 description
=> "the task ID.",
3322 my $rpcenv = PVE
::RPCEnvironment
::get
();
3324 my $authuser = $rpcenv->get_user();
3326 my $node = extract_param
($param, 'node');
3328 my $vmid = extract_param
($param, 'vmid');
3330 my $snapname = extract_param
($param, 'snapname');
3332 die "unable to use snapshot name 'current' (reserved name)\n"
3333 if $snapname eq 'current';
3336 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3337 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3338 $param->{description
});
3341 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3344 __PACKAGE__-
>register_method({
3345 name
=> 'snapshot_cmd_idx',
3346 path
=> '{vmid}/snapshot/{snapname}',
3353 additionalProperties
=> 0,
3355 vmid
=> get_standard_option
('pve-vmid'),
3356 node
=> get_standard_option
('pve-node'),
3357 snapname
=> get_standard_option
('pve-snapshot-name'),
3366 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3373 push @$res, { cmd
=> 'rollback' };
3374 push @$res, { cmd
=> 'config' };
3379 __PACKAGE__-
>register_method({
3380 name
=> 'update_snapshot_config',
3381 path
=> '{vmid}/snapshot/{snapname}/config',
3385 description
=> "Update snapshot metadata.",
3387 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
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'),
3398 description
=> "A textual description or comment.",
3402 returns
=> { type
=> 'null' },
3406 my $rpcenv = PVE
::RPCEnvironment
::get
();
3408 my $authuser = $rpcenv->get_user();
3410 my $vmid = extract_param
($param, 'vmid');
3412 my $snapname = extract_param
($param, 'snapname');
3414 return undef if !defined($param->{description
});
3416 my $updatefn = sub {
3418 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3420 PVE
::QemuConfig-
>check_lock($conf);
3422 my $snap = $conf->{snapshots
}->{$snapname};
3424 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3426 $snap->{description
} = $param->{description
} if defined($param->{description
});
3428 PVE
::QemuConfig-
>write_config($vmid, $conf);
3431 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3436 __PACKAGE__-
>register_method({
3437 name
=> 'get_snapshot_config',
3438 path
=> '{vmid}/snapshot/{snapname}/config',
3441 description
=> "Get snapshot configuration",
3443 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3446 additionalProperties
=> 0,
3448 node
=> get_standard_option
('pve-node'),
3449 vmid
=> get_standard_option
('pve-vmid'),
3450 snapname
=> get_standard_option
('pve-snapshot-name'),
3453 returns
=> { type
=> "object" },
3457 my $rpcenv = PVE
::RPCEnvironment
::get
();
3459 my $authuser = $rpcenv->get_user();
3461 my $vmid = extract_param
($param, 'vmid');
3463 my $snapname = extract_param
($param, 'snapname');
3465 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3467 my $snap = $conf->{snapshots
}->{$snapname};
3469 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3474 __PACKAGE__-
>register_method({
3476 path
=> '{vmid}/snapshot/{snapname}/rollback',
3480 description
=> "Rollback VM state to specified snapshot.",
3482 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3485 additionalProperties
=> 0,
3487 node
=> get_standard_option
('pve-node'),
3488 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3489 snapname
=> get_standard_option
('pve-snapshot-name'),
3494 description
=> "the task ID.",
3499 my $rpcenv = PVE
::RPCEnvironment
::get
();
3501 my $authuser = $rpcenv->get_user();
3503 my $node = extract_param
($param, 'node');
3505 my $vmid = extract_param
($param, 'vmid');
3507 my $snapname = extract_param
($param, 'snapname');
3510 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3511 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3515 # hold migration lock, this makes sure that nobody create replication snapshots
3516 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3519 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3522 __PACKAGE__-
>register_method({
3523 name
=> 'delsnapshot',
3524 path
=> '{vmid}/snapshot/{snapname}',
3528 description
=> "Delete a VM snapshot.",
3530 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3533 additionalProperties
=> 0,
3535 node
=> get_standard_option
('pve-node'),
3536 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3537 snapname
=> get_standard_option
('pve-snapshot-name'),
3541 description
=> "For removal from config file, even if removing disk snapshots fails.",
3547 description
=> "the task ID.",
3552 my $rpcenv = PVE
::RPCEnvironment
::get
();
3554 my $authuser = $rpcenv->get_user();
3556 my $node = extract_param
($param, 'node');
3558 my $vmid = extract_param
($param, 'vmid');
3560 my $snapname = extract_param
($param, 'snapname');
3563 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3564 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3567 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3570 __PACKAGE__-
>register_method({
3572 path
=> '{vmid}/template',
3576 description
=> "Create a Template.",
3578 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3579 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3582 additionalProperties
=> 0,
3584 node
=> get_standard_option
('pve-node'),
3585 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3589 description
=> "If you want to convert only 1 disk to base image.",
3590 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3595 returns
=> { type
=> 'null'},
3599 my $rpcenv = PVE
::RPCEnvironment
::get
();
3601 my $authuser = $rpcenv->get_user();
3603 my $node = extract_param
($param, 'node');
3605 my $vmid = extract_param
($param, 'vmid');
3607 my $disk = extract_param
($param, 'disk');
3609 my $updatefn = sub {
3611 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3613 PVE
::QemuConfig-
>check_lock($conf);
3615 die "unable to create template, because VM contains snapshots\n"
3616 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3618 die "you can't convert a template to a template\n"
3619 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3621 die "you can't convert a VM to template if VM is running\n"
3622 if PVE
::QemuServer
::check_running
($vmid);
3625 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3628 $conf->{template
} = 1;
3629 PVE
::QemuConfig-
>write_config($vmid, $conf);
3631 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3634 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);