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
}, '-escape', '0' ];
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, '-escape', '0'];
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
});
1763 $status->{agent
} = 1 if $conf->{agent
};
1768 __PACKAGE__-
>register_method({
1770 path
=> '{vmid}/status/start',
1774 description
=> "Start virtual machine.",
1776 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1779 additionalProperties
=> 0,
1781 node
=> get_standard_option
('pve-node'),
1782 vmid
=> get_standard_option
('pve-vmid',
1783 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1784 skiplock
=> get_standard_option
('skiplock'),
1785 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1786 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1789 enum
=> ['secure', 'insecure'],
1790 description
=> "Migration traffic is encrypted using an SSH " .
1791 "tunnel by default. On secure, completely private networks " .
1792 "this can be disabled to increase performance.",
1795 migration_network
=> {
1796 type
=> 'string', format
=> 'CIDR',
1797 description
=> "CIDR of the (sub) network that is used for migration.",
1800 machine
=> get_standard_option
('pve-qm-machine'),
1802 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1814 my $rpcenv = PVE
::RPCEnvironment
::get
();
1816 my $authuser = $rpcenv->get_user();
1818 my $node = extract_param
($param, 'node');
1820 my $vmid = extract_param
($param, 'vmid');
1822 my $machine = extract_param
($param, 'machine');
1824 my $stateuri = extract_param
($param, 'stateuri');
1825 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1826 if $stateuri && $authuser ne 'root@pam';
1828 my $skiplock = extract_param
($param, 'skiplock');
1829 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1830 if $skiplock && $authuser ne 'root@pam';
1832 my $migratedfrom = extract_param
($param, 'migratedfrom');
1833 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1834 if $migratedfrom && $authuser ne 'root@pam';
1836 my $migration_type = extract_param
($param, 'migration_type');
1837 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1838 if $migration_type && $authuser ne 'root@pam';
1840 my $migration_network = extract_param
($param, 'migration_network');
1841 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1842 if $migration_network && $authuser ne 'root@pam';
1844 my $targetstorage = extract_param
($param, 'targetstorage');
1845 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1846 if $targetstorage && $authuser ne 'root@pam';
1848 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1849 if $targetstorage && !$migratedfrom;
1851 # read spice ticket from STDIN
1853 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1854 if (defined(my $line = <STDIN
>)) {
1856 $spice_ticket = $line;
1860 PVE
::Cluster
::check_cfs_quorum
();
1862 my $storecfg = PVE
::Storage
::config
();
1864 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1865 $rpcenv->{type
} ne 'ha') {
1870 my $service = "vm:$vmid";
1872 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1874 print "Requesting HA start for VM $vmid\n";
1876 PVE
::Tools
::run_command
($cmd);
1881 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1888 syslog
('info', "start VM $vmid: $upid\n");
1890 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1891 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
1896 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1900 __PACKAGE__-
>register_method({
1902 path
=> '{vmid}/status/stop',
1906 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
1907 "is akin to pulling the power plug of a running computer and may damage the VM data",
1909 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1912 additionalProperties
=> 0,
1914 node
=> get_standard_option
('pve-node'),
1915 vmid
=> get_standard_option
('pve-vmid',
1916 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
1917 skiplock
=> get_standard_option
('skiplock'),
1918 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1920 description
=> "Wait maximal timeout seconds.",
1926 description
=> "Do not deactivate storage volumes.",
1939 my $rpcenv = PVE
::RPCEnvironment
::get
();
1941 my $authuser = $rpcenv->get_user();
1943 my $node = extract_param
($param, 'node');
1945 my $vmid = extract_param
($param, 'vmid');
1947 my $skiplock = extract_param
($param, 'skiplock');
1948 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1949 if $skiplock && $authuser ne 'root@pam';
1951 my $keepActive = extract_param
($param, 'keepActive');
1952 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1953 if $keepActive && $authuser ne 'root@pam';
1955 my $migratedfrom = extract_param
($param, 'migratedfrom');
1956 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1957 if $migratedfrom && $authuser ne 'root@pam';
1960 my $storecfg = PVE
::Storage
::config
();
1962 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1967 my $service = "vm:$vmid";
1969 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
1971 print "Requesting HA stop for VM $vmid\n";
1973 PVE
::Tools
::run_command
($cmd);
1978 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1984 syslog
('info', "stop VM $vmid: $upid\n");
1986 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1987 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1992 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1996 __PACKAGE__-
>register_method({
1998 path
=> '{vmid}/status/reset',
2002 description
=> "Reset virtual machine.",
2004 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2007 additionalProperties
=> 0,
2009 node
=> get_standard_option
('pve-node'),
2010 vmid
=> get_standard_option
('pve-vmid',
2011 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2012 skiplock
=> get_standard_option
('skiplock'),
2021 my $rpcenv = PVE
::RPCEnvironment
::get
();
2023 my $authuser = $rpcenv->get_user();
2025 my $node = extract_param
($param, 'node');
2027 my $vmid = extract_param
($param, 'vmid');
2029 my $skiplock = extract_param
($param, 'skiplock');
2030 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2031 if $skiplock && $authuser ne 'root@pam';
2033 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2038 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2043 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2046 __PACKAGE__-
>register_method({
2047 name
=> 'vm_shutdown',
2048 path
=> '{vmid}/status/shutdown',
2052 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2053 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2055 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2058 additionalProperties
=> 0,
2060 node
=> get_standard_option
('pve-node'),
2061 vmid
=> get_standard_option
('pve-vmid',
2062 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2063 skiplock
=> get_standard_option
('skiplock'),
2065 description
=> "Wait maximal timeout seconds.",
2071 description
=> "Make sure the VM stops.",
2077 description
=> "Do not deactivate storage volumes.",
2090 my $rpcenv = PVE
::RPCEnvironment
::get
();
2092 my $authuser = $rpcenv->get_user();
2094 my $node = extract_param
($param, 'node');
2096 my $vmid = extract_param
($param, 'vmid');
2098 my $skiplock = extract_param
($param, 'skiplock');
2099 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2100 if $skiplock && $authuser ne 'root@pam';
2102 my $keepActive = extract_param
($param, 'keepActive');
2103 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2104 if $keepActive && $authuser ne 'root@pam';
2106 my $storecfg = PVE
::Storage
::config
();
2110 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2111 # otherwise, we will infer a shutdown command, but run into the timeout,
2112 # then when the vm is resumed, it will instantly shutdown
2114 # checking the qmp status here to get feedback to the gui/cli/api
2115 # and the status query should not take too long
2118 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2122 if (!$err && $qmpstatus->{status
} eq "paused") {
2123 if ($param->{forceStop
}) {
2124 warn "VM is paused - stop instead of shutdown\n";
2127 die "VM is paused - cannot shutdown\n";
2131 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2132 ($rpcenv->{type
} ne 'ha')) {
2137 my $service = "vm:$vmid";
2139 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2141 print "Requesting HA stop for VM $vmid\n";
2143 PVE
::Tools
::run_command
($cmd);
2148 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2155 syslog
('info', "shutdown VM $vmid: $upid\n");
2157 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2158 $shutdown, $param->{forceStop
}, $keepActive);
2163 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2167 __PACKAGE__-
>register_method({
2168 name
=> 'vm_suspend',
2169 path
=> '{vmid}/status/suspend',
2173 description
=> "Suspend virtual machine.",
2175 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2178 additionalProperties
=> 0,
2180 node
=> get_standard_option
('pve-node'),
2181 vmid
=> get_standard_option
('pve-vmid',
2182 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2183 skiplock
=> get_standard_option
('skiplock'),
2192 my $rpcenv = PVE
::RPCEnvironment
::get
();
2194 my $authuser = $rpcenv->get_user();
2196 my $node = extract_param
($param, 'node');
2198 my $vmid = extract_param
($param, 'vmid');
2200 my $skiplock = extract_param
($param, 'skiplock');
2201 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2202 if $skiplock && $authuser ne 'root@pam';
2204 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2209 syslog
('info', "suspend VM $vmid: $upid\n");
2211 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2216 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2219 __PACKAGE__-
>register_method({
2220 name
=> 'vm_resume',
2221 path
=> '{vmid}/status/resume',
2225 description
=> "Resume virtual machine.",
2227 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2230 additionalProperties
=> 0,
2232 node
=> get_standard_option
('pve-node'),
2233 vmid
=> get_standard_option
('pve-vmid',
2234 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2235 skiplock
=> get_standard_option
('skiplock'),
2236 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2246 my $rpcenv = PVE
::RPCEnvironment
::get
();
2248 my $authuser = $rpcenv->get_user();
2250 my $node = extract_param
($param, 'node');
2252 my $vmid = extract_param
($param, 'vmid');
2254 my $skiplock = extract_param
($param, 'skiplock');
2255 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2256 if $skiplock && $authuser ne 'root@pam';
2258 my $nocheck = extract_param
($param, 'nocheck');
2260 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2265 syslog
('info', "resume VM $vmid: $upid\n");
2267 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2272 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2275 __PACKAGE__-
>register_method({
2276 name
=> 'vm_sendkey',
2277 path
=> '{vmid}/sendkey',
2281 description
=> "Send key event to virtual machine.",
2283 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2286 additionalProperties
=> 0,
2288 node
=> get_standard_option
('pve-node'),
2289 vmid
=> get_standard_option
('pve-vmid',
2290 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2291 skiplock
=> get_standard_option
('skiplock'),
2293 description
=> "The key (qemu monitor encoding).",
2298 returns
=> { type
=> 'null'},
2302 my $rpcenv = PVE
::RPCEnvironment
::get
();
2304 my $authuser = $rpcenv->get_user();
2306 my $node = extract_param
($param, 'node');
2308 my $vmid = extract_param
($param, 'vmid');
2310 my $skiplock = extract_param
($param, 'skiplock');
2311 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2312 if $skiplock && $authuser ne 'root@pam';
2314 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2319 __PACKAGE__-
>register_method({
2320 name
=> 'vm_feature',
2321 path
=> '{vmid}/feature',
2325 description
=> "Check if feature for virtual machine is available.",
2327 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2330 additionalProperties
=> 0,
2332 node
=> get_standard_option
('pve-node'),
2333 vmid
=> get_standard_option
('pve-vmid'),
2335 description
=> "Feature to check.",
2337 enum
=> [ 'snapshot', 'clone', 'copy' ],
2339 snapname
=> get_standard_option
('pve-snapshot-name', {
2347 hasFeature
=> { type
=> 'boolean' },
2350 items
=> { type
=> 'string' },
2357 my $node = extract_param
($param, 'node');
2359 my $vmid = extract_param
($param, 'vmid');
2361 my $snapname = extract_param
($param, 'snapname');
2363 my $feature = extract_param
($param, 'feature');
2365 my $running = PVE
::QemuServer
::check_running
($vmid);
2367 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2370 my $snap = $conf->{snapshots
}->{$snapname};
2371 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2374 my $storecfg = PVE
::Storage
::config
();
2376 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2377 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2380 hasFeature
=> $hasFeature,
2381 nodes
=> [ keys %$nodelist ],
2385 __PACKAGE__-
>register_method({
2387 path
=> '{vmid}/clone',
2391 description
=> "Create a copy of virtual machine/template.",
2393 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2394 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2395 "'Datastore.AllocateSpace' on any used storage.",
2398 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2400 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2401 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2406 additionalProperties
=> 0,
2408 node
=> get_standard_option
('pve-node'),
2409 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2410 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2413 type
=> 'string', format
=> 'dns-name',
2414 description
=> "Set a name for the new VM.",
2419 description
=> "Description for the new VM.",
2423 type
=> 'string', format
=> 'pve-poolid',
2424 description
=> "Add the new VM to the specified pool.",
2426 snapname
=> get_standard_option
('pve-snapshot-name', {
2429 storage
=> get_standard_option
('pve-storage-id', {
2430 description
=> "Target storage for full clone.",
2435 description
=> "Target format for file storage.",
2439 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2444 description
=> "Create a full copy of all disk. This is always done when " .
2445 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2448 target
=> get_standard_option
('pve-node', {
2449 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2460 my $rpcenv = PVE
::RPCEnvironment
::get
();
2462 my $authuser = $rpcenv->get_user();
2464 my $node = extract_param
($param, 'node');
2466 my $vmid = extract_param
($param, 'vmid');
2468 my $newid = extract_param
($param, 'newid');
2470 my $pool = extract_param
($param, 'pool');
2472 if (defined($pool)) {
2473 $rpcenv->check_pool_exist($pool);
2476 my $snapname = extract_param
($param, 'snapname');
2478 my $storage = extract_param
($param, 'storage');
2480 my $format = extract_param
($param, 'format');
2482 my $target = extract_param
($param, 'target');
2484 my $localnode = PVE
::INotify
::nodename
();
2486 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2488 PVE
::Cluster
::check_node_exists
($target) if $target;
2490 my $storecfg = PVE
::Storage
::config
();
2493 # check if storage is enabled on local node
2494 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2496 # check if storage is available on target node
2497 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2498 # clone only works if target storage is shared
2499 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2500 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2504 PVE
::Cluster
::check_cfs_quorum
();
2506 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2508 # exclusive lock if VM is running - else shared lock is enough;
2509 my $shared_lock = $running ?
0 : 1;
2513 # do all tests after lock
2514 # we also try to do all tests before we fork the worker
2516 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2518 PVE
::QemuConfig-
>check_lock($conf);
2520 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2522 die "unexpected state change\n" if $verify_running != $running;
2524 die "snapshot '$snapname' does not exist\n"
2525 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2527 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2529 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2531 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2533 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2535 die "unable to create VM $newid: config file already exists\n"
2538 my $newconf = { lock => 'clone' };
2543 foreach my $opt (keys %$oldconf) {
2544 my $value = $oldconf->{$opt};
2546 # do not copy snapshot related info
2547 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2548 $opt eq 'vmstate' || $opt eq 'snapstate';
2550 # no need to copy unused images, because VMID(owner) changes anyways
2551 next if $opt =~ m/^unused\d+$/;
2553 # always change MAC! address
2554 if ($opt =~ m/^net(\d+)$/) {
2555 my $net = PVE
::QemuServer
::parse_net
($value);
2556 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2557 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2558 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2559 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2560 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2561 die "unable to parse drive options for '$opt'\n" if !$drive;
2562 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2563 $newconf->{$opt} = $value; # simply copy configuration
2565 if ($param->{full
}) {
2566 die "Full clone feature is not supported for drive '$opt'\n"
2567 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2568 $fullclone->{$opt} = 1;
2570 # not full means clone instead of copy
2571 die "Linked clone feature is not supported for drive '$opt'\n"
2572 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2574 $drives->{$opt} = $drive;
2575 push @$vollist, $drive->{file
};
2578 # copy everything else
2579 $newconf->{$opt} = $value;
2583 # auto generate a new uuid
2584 my ($uuid, $uuid_str);
2585 UUID
::generate
($uuid);
2586 UUID
::unparse
($uuid, $uuid_str);
2587 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2588 $smbios1->{uuid
} = $uuid_str;
2589 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2591 delete $newconf->{template
};
2593 if ($param->{name
}) {
2594 $newconf->{name
} = $param->{name
};
2596 if ($oldconf->{name
}) {
2597 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2599 $newconf->{name
} = "Copy-of-VM-$vmid";
2603 if ($param->{description
}) {
2604 $newconf->{description
} = $param->{description
};
2607 # create empty/temp config - this fails if VM already exists on other node
2608 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2613 my $newvollist = [];
2620 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2622 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2624 my $total_jobs = scalar(keys %{$drives});
2627 foreach my $opt (keys %$drives) {
2628 my $drive = $drives->{$opt};
2629 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2631 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2632 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2633 $jobs, $skipcomplete, $oldconf->{agent
});
2635 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2637 PVE
::QemuConfig-
>write_config($newid, $newconf);
2641 delete $newconf->{lock};
2642 PVE
::QemuConfig-
>write_config($newid, $newconf);
2645 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2646 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2647 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2649 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2650 die "Failed to move config to node '$target' - rename failed: $!\n"
2651 if !rename($conffile, $newconffile);
2654 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2659 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2661 sleep 1; # some storage like rbd need to wait before release volume - really?
2663 foreach my $volid (@$newvollist) {
2664 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2667 die "clone failed: $err";
2673 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2675 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2678 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2679 # Aquire exclusive lock lock for $newid
2680 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2685 __PACKAGE__-
>register_method({
2686 name
=> 'move_vm_disk',
2687 path
=> '{vmid}/move_disk',
2691 description
=> "Move volume to different storage.",
2693 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2695 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2696 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2700 additionalProperties
=> 0,
2702 node
=> get_standard_option
('pve-node'),
2703 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2706 description
=> "The disk you want to move.",
2707 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2709 storage
=> get_standard_option
('pve-storage-id', {
2710 description
=> "Target storage.",
2711 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2715 description
=> "Target Format.",
2716 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2721 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2727 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2735 description
=> "the task ID.",
2740 my $rpcenv = PVE
::RPCEnvironment
::get
();
2742 my $authuser = $rpcenv->get_user();
2744 my $node = extract_param
($param, 'node');
2746 my $vmid = extract_param
($param, 'vmid');
2748 my $digest = extract_param
($param, 'digest');
2750 my $disk = extract_param
($param, 'disk');
2752 my $storeid = extract_param
($param, 'storage');
2754 my $format = extract_param
($param, 'format');
2756 my $storecfg = PVE
::Storage
::config
();
2758 my $updatefn = sub {
2760 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2762 PVE
::QemuConfig-
>check_lock($conf);
2764 die "checksum missmatch (file change by other user?)\n"
2765 if $digest && $digest ne $conf->{digest
};
2767 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2769 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2771 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2773 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2776 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2777 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2781 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2782 (!$format || !$oldfmt || $oldfmt eq $format);
2784 # this only checks snapshots because $disk is passed!
2785 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2786 die "you can't move a disk with snapshots and delete the source\n"
2787 if $snapshotted && $param->{delete};
2789 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2791 my $running = PVE
::QemuServer
::check_running
($vmid);
2793 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2797 my $newvollist = [];
2803 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2805 warn "moving disk with snapshots, snapshots will not be moved!\n"
2808 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2809 $vmid, $storeid, $format, 1, $newvollist);
2811 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2813 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2815 # convert moved disk to base if part of template
2816 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2817 if PVE
::QemuConfig-
>is_template($conf);
2819 PVE
::QemuConfig-
>write_config($vmid, $conf);
2822 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2823 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2830 foreach my $volid (@$newvollist) {
2831 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2834 die "storage migration failed: $err";
2837 if ($param->{delete}) {
2839 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2840 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2846 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2849 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2852 __PACKAGE__-
>register_method({
2853 name
=> 'migrate_vm',
2854 path
=> '{vmid}/migrate',
2858 description
=> "Migrate virtual machine. Creates a new migration task.",
2860 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2863 additionalProperties
=> 0,
2865 node
=> get_standard_option
('pve-node'),
2866 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2867 target
=> get_standard_option
('pve-node', {
2868 description
=> "Target node.",
2869 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2873 description
=> "Use online/live migration.",
2878 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2883 enum
=> ['secure', 'insecure'],
2884 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
2887 migration_network
=> {
2888 type
=> 'string', format
=> 'CIDR',
2889 description
=> "CIDR of the (sub) network that is used for migration.",
2892 "with-local-disks" => {
2894 description
=> "Enable live storage migration for local disk",
2897 targetstorage
=> get_standard_option
('pve-storage-id', {
2898 description
=> "Default target storage.",
2900 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2906 description
=> "the task ID.",
2911 my $rpcenv = PVE
::RPCEnvironment
::get
();
2913 my $authuser = $rpcenv->get_user();
2915 my $target = extract_param
($param, 'target');
2917 my $localnode = PVE
::INotify
::nodename
();
2918 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2920 PVE
::Cluster
::check_cfs_quorum
();
2922 PVE
::Cluster
::check_node_exists
($target);
2924 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2926 my $vmid = extract_param
($param, 'vmid');
2928 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
2929 if !$param->{online
} && $param->{targetstorage
};
2931 raise_param_exc
({ force
=> "Only root may use this option." })
2932 if $param->{force
} && $authuser ne 'root@pam';
2934 raise_param_exc
({ migration_type
=> "Only root may use this option." })
2935 if $param->{migration_type
} && $authuser ne 'root@pam';
2937 # allow root only until better network permissions are available
2938 raise_param_exc
({ migration_network
=> "Only root may use this option." })
2939 if $param->{migration_network
} && $authuser ne 'root@pam';
2942 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2944 # try to detect errors early
2946 PVE
::QemuConfig-
>check_lock($conf);
2948 if (PVE
::QemuServer
::check_running
($vmid)) {
2949 die "cant migrate running VM without --online\n"
2950 if !$param->{online
};
2953 my $storecfg = PVE
::Storage
::config
();
2955 if( $param->{targetstorage
}) {
2956 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
2958 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2961 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2966 my $service = "vm:$vmid";
2968 my $cmd = ['ha-manager', 'migrate', $service, $target];
2970 print "Requesting HA migration for VM $vmid to node $target\n";
2972 PVE
::Tools
::run_command
($cmd);
2977 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2982 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2986 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
2989 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
2994 __PACKAGE__-
>register_method({
2996 path
=> '{vmid}/monitor',
3000 description
=> "Execute Qemu monitor commands.",
3002 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3003 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3006 additionalProperties
=> 0,
3008 node
=> get_standard_option
('pve-node'),
3009 vmid
=> get_standard_option
('pve-vmid'),
3012 description
=> "The monitor command.",
3016 returns
=> { type
=> 'string'},
3020 my $rpcenv = PVE
::RPCEnvironment
::get
();
3021 my $authuser = $rpcenv->get_user();
3024 my $command = shift;
3025 return $command =~ m/^\s*info(\s+|$)/
3026 || $command =~ m/^\s*help\s*$/;
3029 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3030 if !&$is_ro($param->{command
});
3032 my $vmid = $param->{vmid
};
3034 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3038 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3040 $res = "ERROR: $@" if $@;
3045 my $guest_agent_commands = [
3053 'network-get-interfaces',
3056 'get-memory-blocks',
3057 'get-memory-block-info',
3064 __PACKAGE__-
>register_method({
3066 path
=> '{vmid}/agent',
3070 description
=> "Execute Qemu Guest Agent commands.",
3072 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3075 additionalProperties
=> 0,
3077 node
=> get_standard_option
('pve-node'),
3078 vmid
=> get_standard_option
('pve-vmid', {
3079 completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
3082 description
=> "The QGA command.",
3083 enum
=> $guest_agent_commands,
3089 description
=> "Returns an object with a single `result` property. The type of that
3090 property depends on the executed command.",
3095 my $vmid = $param->{vmid
};
3097 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3099 die "No Qemu Guest Agent\n" if !defined($conf->{agent
});
3100 die "VM $vmid is not running\n" if !PVE
::QemuServer
::check_running
($vmid);
3102 my $cmd = $param->{command
};
3104 my $res = PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-$cmd");
3106 return { result
=> $res };
3109 __PACKAGE__-
>register_method({
3110 name
=> 'resize_vm',
3111 path
=> '{vmid}/resize',
3115 description
=> "Extend volume size.",
3117 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3120 additionalProperties
=> 0,
3122 node
=> get_standard_option
('pve-node'),
3123 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3124 skiplock
=> get_standard_option
('skiplock'),
3127 description
=> "The disk you want to resize.",
3128 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3132 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3133 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.",
3137 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3143 returns
=> { type
=> 'null'},
3147 my $rpcenv = PVE
::RPCEnvironment
::get
();
3149 my $authuser = $rpcenv->get_user();
3151 my $node = extract_param
($param, 'node');
3153 my $vmid = extract_param
($param, 'vmid');
3155 my $digest = extract_param
($param, 'digest');
3157 my $disk = extract_param
($param, 'disk');
3159 my $sizestr = extract_param
($param, 'size');
3161 my $skiplock = extract_param
($param, 'skiplock');
3162 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3163 if $skiplock && $authuser ne 'root@pam';
3165 my $storecfg = PVE
::Storage
::config
();
3167 my $updatefn = sub {
3169 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3171 die "checksum missmatch (file change by other user?)\n"
3172 if $digest && $digest ne $conf->{digest
};
3173 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3175 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3177 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3179 my (undef, undef, undef, undef, undef, undef, $format) =
3180 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3182 die "can't resize volume: $disk if snapshot exists\n"
3183 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3185 my $volid = $drive->{file
};
3187 die "disk '$disk' has no associated volume\n" if !$volid;
3189 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3191 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3193 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3195 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3196 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3198 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3199 my ($ext, $newsize, $unit) = ($1, $2, $4);
3202 $newsize = $newsize * 1024;
3203 } elsif ($unit eq 'M') {
3204 $newsize = $newsize * 1024 * 1024;
3205 } elsif ($unit eq 'G') {
3206 $newsize = $newsize * 1024 * 1024 * 1024;
3207 } elsif ($unit eq 'T') {
3208 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3211 $newsize += $size if $ext;
3212 $newsize = int($newsize);
3214 die "shrinking disks is not supported\n" if $newsize < $size;
3216 return if $size == $newsize;
3218 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3220 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3222 $drive->{size
} = $newsize;
3223 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3225 PVE
::QemuConfig-
>write_config($vmid, $conf);
3228 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3232 __PACKAGE__-
>register_method({
3233 name
=> 'snapshot_list',
3234 path
=> '{vmid}/snapshot',
3236 description
=> "List all snapshots.",
3238 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3241 protected
=> 1, # qemu pid files are only readable by root
3243 additionalProperties
=> 0,
3245 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3246 node
=> get_standard_option
('pve-node'),
3255 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3260 my $vmid = $param->{vmid
};
3262 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3263 my $snaphash = $conf->{snapshots
} || {};
3267 foreach my $name (keys %$snaphash) {
3268 my $d = $snaphash->{$name};
3271 snaptime
=> $d->{snaptime
} || 0,
3272 vmstate
=> $d->{vmstate
} ?
1 : 0,
3273 description
=> $d->{description
} || '',
3275 $item->{parent
} = $d->{parent
} if $d->{parent
};
3276 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3280 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3281 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3282 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3284 push @$res, $current;
3289 __PACKAGE__-
>register_method({
3291 path
=> '{vmid}/snapshot',
3295 description
=> "Snapshot a VM.",
3297 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3300 additionalProperties
=> 0,
3302 node
=> get_standard_option
('pve-node'),
3303 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3304 snapname
=> get_standard_option
('pve-snapshot-name'),
3308 description
=> "Save the vmstate",
3313 description
=> "A textual description or comment.",
3319 description
=> "the task ID.",
3324 my $rpcenv = PVE
::RPCEnvironment
::get
();
3326 my $authuser = $rpcenv->get_user();
3328 my $node = extract_param
($param, 'node');
3330 my $vmid = extract_param
($param, 'vmid');
3332 my $snapname = extract_param
($param, 'snapname');
3334 die "unable to use snapshot name 'current' (reserved name)\n"
3335 if $snapname eq 'current';
3338 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3339 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3340 $param->{description
});
3343 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3346 __PACKAGE__-
>register_method({
3347 name
=> 'snapshot_cmd_idx',
3348 path
=> '{vmid}/snapshot/{snapname}',
3355 additionalProperties
=> 0,
3357 vmid
=> get_standard_option
('pve-vmid'),
3358 node
=> get_standard_option
('pve-node'),
3359 snapname
=> get_standard_option
('pve-snapshot-name'),
3368 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3375 push @$res, { cmd
=> 'rollback' };
3376 push @$res, { cmd
=> 'config' };
3381 __PACKAGE__-
>register_method({
3382 name
=> 'update_snapshot_config',
3383 path
=> '{vmid}/snapshot/{snapname}/config',
3387 description
=> "Update snapshot metadata.",
3389 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3392 additionalProperties
=> 0,
3394 node
=> get_standard_option
('pve-node'),
3395 vmid
=> get_standard_option
('pve-vmid'),
3396 snapname
=> get_standard_option
('pve-snapshot-name'),
3400 description
=> "A textual description or comment.",
3404 returns
=> { type
=> 'null' },
3408 my $rpcenv = PVE
::RPCEnvironment
::get
();
3410 my $authuser = $rpcenv->get_user();
3412 my $vmid = extract_param
($param, 'vmid');
3414 my $snapname = extract_param
($param, 'snapname');
3416 return undef if !defined($param->{description
});
3418 my $updatefn = sub {
3420 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3422 PVE
::QemuConfig-
>check_lock($conf);
3424 my $snap = $conf->{snapshots
}->{$snapname};
3426 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3428 $snap->{description
} = $param->{description
} if defined($param->{description
});
3430 PVE
::QemuConfig-
>write_config($vmid, $conf);
3433 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3438 __PACKAGE__-
>register_method({
3439 name
=> 'get_snapshot_config',
3440 path
=> '{vmid}/snapshot/{snapname}/config',
3443 description
=> "Get snapshot configuration",
3445 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3448 additionalProperties
=> 0,
3450 node
=> get_standard_option
('pve-node'),
3451 vmid
=> get_standard_option
('pve-vmid'),
3452 snapname
=> get_standard_option
('pve-snapshot-name'),
3455 returns
=> { type
=> "object" },
3459 my $rpcenv = PVE
::RPCEnvironment
::get
();
3461 my $authuser = $rpcenv->get_user();
3463 my $vmid = extract_param
($param, 'vmid');
3465 my $snapname = extract_param
($param, 'snapname');
3467 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3469 my $snap = $conf->{snapshots
}->{$snapname};
3471 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3476 __PACKAGE__-
>register_method({
3478 path
=> '{vmid}/snapshot/{snapname}/rollback',
3482 description
=> "Rollback VM state to specified snapshot.",
3484 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3487 additionalProperties
=> 0,
3489 node
=> get_standard_option
('pve-node'),
3490 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3491 snapname
=> get_standard_option
('pve-snapshot-name'),
3496 description
=> "the task ID.",
3501 my $rpcenv = PVE
::RPCEnvironment
::get
();
3503 my $authuser = $rpcenv->get_user();
3505 my $node = extract_param
($param, 'node');
3507 my $vmid = extract_param
($param, 'vmid');
3509 my $snapname = extract_param
($param, 'snapname');
3512 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3513 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3517 # hold migration lock, this makes sure that nobody create replication snapshots
3518 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3521 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3524 __PACKAGE__-
>register_method({
3525 name
=> 'delsnapshot',
3526 path
=> '{vmid}/snapshot/{snapname}',
3530 description
=> "Delete a VM snapshot.",
3532 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3535 additionalProperties
=> 0,
3537 node
=> get_standard_option
('pve-node'),
3538 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3539 snapname
=> get_standard_option
('pve-snapshot-name'),
3543 description
=> "For removal from config file, even if removing disk snapshots fails.",
3549 description
=> "the task ID.",
3554 my $rpcenv = PVE
::RPCEnvironment
::get
();
3556 my $authuser = $rpcenv->get_user();
3558 my $node = extract_param
($param, 'node');
3560 my $vmid = extract_param
($param, 'vmid');
3562 my $snapname = extract_param
($param, 'snapname');
3565 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3566 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3569 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3572 __PACKAGE__-
>register_method({
3574 path
=> '{vmid}/template',
3578 description
=> "Create a Template.",
3580 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3581 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3584 additionalProperties
=> 0,
3586 node
=> get_standard_option
('pve-node'),
3587 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3591 description
=> "If you want to convert only 1 disk to base image.",
3592 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3597 returns
=> { type
=> 'null'},
3601 my $rpcenv = PVE
::RPCEnvironment
::get
();
3603 my $authuser = $rpcenv->get_user();
3605 my $node = extract_param
($param, 'node');
3607 my $vmid = extract_param
($param, 'vmid');
3609 my $disk = extract_param
($param, 'disk');
3611 my $updatefn = sub {
3613 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3615 PVE
::QemuConfig-
>check_lock($conf);
3617 die "unable to create template, because VM contains snapshots\n"
3618 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3620 die "you can't convert a template to a template\n"
3621 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3623 die "you can't convert a VM to template if VM is running\n"
3624 if PVE
::QemuServer
::check_running
($vmid);
3627 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3630 $conf->{template
} = 1;
3631 PVE
::QemuConfig-
>write_config($vmid, $conf);
3633 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3636 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);