1 package PVE
::API2
::Qemu
;
9 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
11 use PVE
::Tools
qw(extract_param);
12 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
14 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::RPCEnvironment
;
19 use PVE
::AccessControl
;
23 use PVE
::API2
::Firewall
::VM
;
26 use Data
::Dumper
; # fixme: remove
28 use base
qw(PVE::RESTHandler);
30 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.";
32 my $resolve_cdrom_alias = sub {
35 if (my $value = $param->{cdrom
}) {
36 $value .= ",media=cdrom" if $value !~ m/media=/;
37 $param->{ide2
} = $value;
38 delete $param->{cdrom
};
42 my $check_storage_access = sub {
43 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
45 PVE
::QemuServer
::foreach_drive
($settings, sub {
46 my ($ds, $drive) = @_;
48 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
50 my $volid = $drive->{file
};
52 if (!$volid || $volid eq 'none') {
54 } elsif ($isCDROM && ($volid eq 'cdrom')) {
55 $rpcenv->check($authuser, "/", ['Sys.Console']);
56 } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
57 my ($storeid, $size) = ($2 || $default_storage, $3);
58 die "no storage ID specified (and no default storage)\n" if !$storeid;
59 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
61 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
66 my $check_storage_access_clone = sub {
67 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
71 PVE
::QemuServer
::foreach_drive
($conf, sub {
72 my ($ds, $drive) = @_;
74 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
76 my $volid = $drive->{file
};
78 return if !$volid || $volid eq 'none';
81 if ($volid eq 'cdrom') {
82 $rpcenv->check($authuser, "/", ['Sys.Console']);
84 # we simply allow access
85 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
86 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
87 $sharedvm = 0 if !$scfg->{shared
};
91 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
92 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
93 $sharedvm = 0 if !$scfg->{shared
};
95 $sid = $storage if $storage;
96 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
103 # Note: $pool is only needed when creating a VM, because pool permissions
104 # are automatically inherited if VM already exists inside a pool.
105 my $create_disks = sub {
106 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
111 PVE
::QemuServer
::foreach_drive
($settings, sub {
112 my ($ds, $disk) = @_;
114 my $volid = $disk->{file
};
116 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
117 delete $disk->{size
};
118 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
119 } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
120 my ($storeid, $size) = ($2 || $default_storage, $3);
121 die "no storage ID specified (and no default storage)\n" if !$storeid;
122 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
123 my $fmt = $disk->{format
} || $defformat;
124 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
125 $fmt, undef, $size*1024*1024);
126 $disk->{file
} = $volid;
127 $disk->{size
} = $size*1024*1024*1024;
128 push @$vollist, $volid;
129 delete $disk->{format
}; # no longer needed
130 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
133 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
135 my $volid_is_new = 1;
138 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
139 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
144 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
146 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
148 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
150 die "volume $volid does not exists\n" if !$size;
152 $disk->{size
} = $size;
155 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
159 # free allocated images on error
161 syslog
('err', "VM $vmid creating disks failed");
162 foreach my $volid (@$vollist) {
163 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
169 # modify vm config if everything went well
170 foreach my $ds (keys %$res) {
171 $conf->{$ds} = $res->{$ds};
177 my $check_vm_modify_config_perm = sub {
178 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
180 return 1 if $authuser eq 'root@pam';
182 foreach my $opt (@$key_list) {
183 # disk checks need to be done somewhere else
184 next if PVE
::QemuServer
::valid_drivename
($opt);
186 if ($opt eq 'sockets' || $opt eq 'cores' ||
187 $opt eq 'cpu' || $opt eq 'smp' || $opt eq 'vcpus' ||
188 $opt eq 'cpulimit' || $opt eq 'cpuunits') {
189 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
190 } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
191 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
192 } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') {
193 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
194 } elsif ($opt eq 'args' || $opt eq 'lock') {
195 die "only root can set '$opt' config\n";
196 } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
197 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
198 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
199 } elsif ($opt =~ m/^net\d+$/) {
200 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
202 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
209 __PACKAGE__-
>register_method({
213 description
=> "Virtual machine index (per node).",
215 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
219 protected
=> 1, # qemu pid files are only readable by root
221 additionalProperties
=> 0,
223 node
=> get_standard_option
('pve-node'),
232 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
237 my $rpcenv = PVE
::RPCEnvironment
::get
();
238 my $authuser = $rpcenv->get_user();
240 my $vmstatus = PVE
::QemuServer
::vmstatus
();
243 foreach my $vmid (keys %$vmstatus) {
244 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
246 my $data = $vmstatus->{$vmid};
247 $data->{vmid
} = int($vmid);
256 __PACKAGE__-
>register_method({
260 description
=> "Create or restore a virtual machine.",
262 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
263 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
264 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
265 user
=> 'all', # check inside
270 additionalProperties
=> 0,
271 properties
=> PVE
::QemuServer
::json_config_properties
(
273 node
=> get_standard_option
('pve-node'),
274 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
276 description
=> "The backup file.",
280 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
282 storage
=> get_standard_option
('pve-storage-id', {
283 description
=> "Default storage.",
285 completion
=> \
&PVE
::QemuServer
::complete_storage
,
290 description
=> "Allow to overwrite existing VM.",
291 requires
=> 'archive',
296 description
=> "Assign a unique random ethernet address.",
297 requires
=> 'archive',
301 type
=> 'string', format
=> 'pve-poolid',
302 description
=> "Add the VM to the specified pool.",
312 my $rpcenv = PVE
::RPCEnvironment
::get
();
314 my $authuser = $rpcenv->get_user();
316 my $node = extract_param
($param, 'node');
318 my $vmid = extract_param
($param, 'vmid');
320 my $archive = extract_param
($param, 'archive');
322 my $storage = extract_param
($param, 'storage');
324 my $force = extract_param
($param, 'force');
326 my $unique = extract_param
($param, 'unique');
328 my $pool = extract_param
($param, 'pool');
330 my $filename = PVE
::QemuServer
::config_file
($vmid);
332 my $storecfg = PVE
::Storage
::config
();
334 PVE
::Cluster
::check_cfs_quorum
();
336 if (defined($pool)) {
337 $rpcenv->check_pool_exist($pool);
340 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
341 if defined($storage);
343 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
345 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
347 } elsif ($archive && $force && (-f
$filename) &&
348 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
349 # OK: user has VM.Backup permissions, and want to restore an existing VM
355 &$resolve_cdrom_alias($param);
357 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
359 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
361 foreach my $opt (keys %$param) {
362 if (PVE
::QemuServer
::valid_drivename
($opt)) {
363 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
364 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
366 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
367 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
371 PVE
::QemuServer
::add_random_macs
($param);
373 my $keystr = join(' ', keys %$param);
374 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
376 if ($archive eq '-') {
377 die "pipe requires cli environment\n"
378 if $rpcenv->{type
} ne 'cli';
380 $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
381 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
385 my $restorefn = sub {
387 # fixme: this test does not work if VM exists on other node!
389 die "unable to restore vm $vmid: config file already exists\n"
392 die "unable to restore vm $vmid: vm is running\n"
393 if PVE
::QemuServer
::check_running
($vmid);
397 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
400 unique
=> $unique });
402 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
405 return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
411 die "unable to create vm $vmid: config file already exists\n"
422 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
424 # try to be smart about bootdisk
425 my @disks = PVE
::QemuServer
::disknames
();
427 foreach my $ds (reverse @disks) {
428 next if !$conf->{$ds};
429 my $disk = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
430 next if PVE
::QemuServer
::drive_is_cdrom
($disk);
434 if (!$conf->{bootdisk
} && $firstdisk) {
435 $conf->{bootdisk
} = $firstdisk;
438 # auto generate uuid if user did not specify smbios1 option
439 if (!$conf->{smbios1
}) {
440 my ($uuid, $uuid_str);
441 UUID
::generate
($uuid);
442 UUID
::unparse
($uuid, $uuid_str);
443 $conf->{smbios1
} = "uuid=$uuid_str";
446 PVE
::QemuServer
::update_config_nolock
($vmid, $conf);
452 foreach my $volid (@$vollist) {
453 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
456 die "create failed - $err";
459 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
462 return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
465 return PVE
::QemuServer
::lock_config_full
($vmid, 1, $archive ?
$restorefn : $createfn);
468 __PACKAGE__-
>register_method({
473 description
=> "Directory index",
478 additionalProperties
=> 0,
480 node
=> get_standard_option
('pve-node'),
481 vmid
=> get_standard_option
('pve-vmid'),
489 subdir
=> { type
=> 'string' },
492 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
498 { subdir
=> 'config' },
499 { subdir
=> 'pending' },
500 { subdir
=> 'status' },
501 { subdir
=> 'unlink' },
502 { subdir
=> 'vncproxy' },
503 { subdir
=> 'migrate' },
504 { subdir
=> 'resize' },
505 { subdir
=> 'move' },
507 { subdir
=> 'rrddata' },
508 { subdir
=> 'monitor' },
509 { subdir
=> 'snapshot' },
510 { subdir
=> 'spiceproxy' },
511 { subdir
=> 'sendkey' },
512 { subdir
=> 'firewall' },
518 __PACKAGE__-
>register_method ({
519 subclass
=> "PVE::API2::Firewall::VM",
520 path
=> '{vmid}/firewall',
523 __PACKAGE__-
>register_method({
525 path
=> '{vmid}/rrd',
527 protected
=> 1, # fixme: can we avoid that?
529 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
531 description
=> "Read VM RRD statistics (returns PNG)",
533 additionalProperties
=> 0,
535 node
=> get_standard_option
('pve-node'),
536 vmid
=> get_standard_option
('pve-vmid'),
538 description
=> "Specify the time frame you are interested in.",
540 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
543 description
=> "The list of datasources you want to display.",
544 type
=> 'string', format
=> 'pve-configid-list',
547 description
=> "The RRD consolidation function",
549 enum
=> [ 'AVERAGE', 'MAX' ],
557 filename
=> { type
=> 'string' },
563 return PVE
::Cluster
::create_rrd_graph
(
564 "pve2-vm/$param->{vmid}", $param->{timeframe
},
565 $param->{ds
}, $param->{cf
});
569 __PACKAGE__-
>register_method({
571 path
=> '{vmid}/rrddata',
573 protected
=> 1, # fixme: can we avoid that?
575 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
577 description
=> "Read VM RRD statistics",
579 additionalProperties
=> 0,
581 node
=> get_standard_option
('pve-node'),
582 vmid
=> get_standard_option
('pve-vmid'),
584 description
=> "Specify the time frame you are interested in.",
586 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
589 description
=> "The RRD consolidation function",
591 enum
=> [ 'AVERAGE', 'MAX' ],
606 return PVE
::Cluster
::create_rrd_data
(
607 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
611 __PACKAGE__-
>register_method({
613 path
=> '{vmid}/config',
616 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
618 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
621 additionalProperties
=> 0,
623 node
=> get_standard_option
('pve-node'),
624 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
626 description
=> "Get current values (instead of pending values).",
638 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
645 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
647 delete $conf->{snapshots
};
649 if (!$param->{current
}) {
650 foreach my $opt (keys %{$conf->{pending
}}) {
651 next if $opt eq 'delete';
652 my $value = $conf->{pending
}->{$opt};
653 next if ref($value); # just to be sure
654 $conf->{$opt} = $value;
656 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
657 foreach my $opt (keys %$pending_delete_hash) {
658 delete $conf->{$opt} if $conf->{$opt};
662 delete $conf->{pending
};
667 __PACKAGE__-
>register_method({
668 name
=> 'vm_pending',
669 path
=> '{vmid}/pending',
672 description
=> "Get virtual machine configuration, including pending changes.",
674 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
677 additionalProperties
=> 0,
679 node
=> get_standard_option
('pve-node'),
680 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
689 description
=> "Configuration option name.",
693 description
=> "Current value.",
698 description
=> "Pending value.",
703 description
=> "Indicates a pending delete request if present and not 0. " .
704 "The value 2 indicates a force-delete request.",
716 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
718 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
722 foreach my $opt (keys %$conf) {
723 next if ref($conf->{$opt});
724 my $item = { key
=> $opt };
725 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
726 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
727 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
731 foreach my $opt (keys %{$conf->{pending
}}) {
732 next if $opt eq 'delete';
733 next if ref($conf->{pending
}->{$opt}); # just to be sure
734 next if defined($conf->{$opt});
735 my $item = { key
=> $opt };
736 $item->{pending
} = $conf->{pending
}->{$opt};
740 while (my ($opt, $force) = each %$pending_delete_hash) {
741 next if $conf->{pending
}->{$opt}; # just to be sure
742 next if $conf->{$opt};
743 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
750 # POST/PUT {vmid}/config implementation
752 # The original API used PUT (idempotent) an we assumed that all operations
753 # are fast. But it turned out that almost any configuration change can
754 # involve hot-plug actions, or disk alloc/free. Such actions can take long
755 # time to complete and have side effects (not idempotent).
757 # The new implementation uses POST and forks a worker process. We added
758 # a new option 'background_delay'. If specified we wait up to
759 # 'background_delay' second for the worker task to complete. It returns null
760 # if the task is finished within that time, else we return the UPID.
762 my $update_vm_api = sub {
763 my ($param, $sync) = @_;
765 my $rpcenv = PVE
::RPCEnvironment
::get
();
767 my $authuser = $rpcenv->get_user();
769 my $node = extract_param
($param, 'node');
771 my $vmid = extract_param
($param, 'vmid');
773 my $digest = extract_param
($param, 'digest');
775 my $background_delay = extract_param
($param, 'background_delay');
777 my @paramarr = (); # used for log message
778 foreach my $key (keys %$param) {
779 push @paramarr, "-$key", $param->{$key};
782 my $skiplock = extract_param
($param, 'skiplock');
783 raise_param_exc
({ skiplock
=> "Only root may use this option." })
784 if $skiplock && $authuser ne 'root@pam';
786 my $delete_str = extract_param
($param, 'delete');
788 my $revert_str = extract_param
($param, 'revert');
790 my $force = extract_param
($param, 'force');
792 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
794 my $storecfg = PVE
::Storage
::config
();
796 my $defaults = PVE
::QemuServer
::load_defaults
();
798 &$resolve_cdrom_alias($param);
800 # now try to verify all parameters
803 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
804 if (!PVE
::QemuServer
::option_exists
($opt)) {
805 raise_param_exc
({ revert
=> "unknown option '$opt'" });
808 raise_param_exc
({ delete => "you can't use '-$opt' and " .
809 "-revert $opt' at the same time" })
810 if defined($param->{$opt});
816 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
817 $opt = 'ide2' if $opt eq 'cdrom';
819 raise_param_exc
({ delete => "you can't use '-$opt' and " .
820 "-delete $opt' at the same time" })
821 if defined($param->{$opt});
823 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
824 "-revert $opt' at the same time" })
827 if (!PVE
::QemuServer
::option_exists
($opt)) {
828 raise_param_exc
({ delete => "unknown option '$opt'" });
834 foreach my $opt (keys %$param) {
835 if (PVE
::QemuServer
::valid_drivename
($opt)) {
837 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
838 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
839 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
840 } elsif ($opt =~ m/^net(\d+)$/) {
842 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
843 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
847 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
849 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
851 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
855 my $conf = PVE
::QemuServer
::load_config
($vmid);
857 die "checksum missmatch (file change by other user?)\n"
858 if $digest && $digest ne $conf->{digest
};
860 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
862 foreach my $opt (keys %$revert) {
863 if (defined($conf->{$opt})) {
864 $param->{$opt} = $conf->{$opt};
865 } elsif (defined($conf->{pending
}->{$opt})) {
870 if ($param->{memory
} || defined($param->{balloon
})) {
871 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
872 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
874 die "balloon value too large (must be smaller than assigned memory)\n"
875 if $balloon && $balloon > $maxmem;
878 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
882 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
884 # write updates to pending section
886 my $modified = {}; # record what $option we modify
888 foreach my $opt (@delete) {
889 $modified->{$opt} = 1;
890 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
891 if ($opt =~ m/^unused/) {
892 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
893 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
894 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
895 delete $conf->{$opt};
896 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
898 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
899 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
900 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
901 if defined($conf->{pending
}->{$opt});
902 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
903 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
905 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
906 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
910 foreach my $opt (keys %$param) { # add/change
911 $modified->{$opt} = 1;
912 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
913 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
915 if (PVE
::QemuServer
::valid_drivename
($opt)) {
916 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
917 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
918 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
920 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
922 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
923 if defined($conf->{pending
}->{$opt});
925 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
927 $conf->{pending
}->{$opt} = $param->{$opt};
929 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
930 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
933 # remove pending changes when nothing changed
934 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
935 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
936 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) if $changes;
938 return if !scalar(keys %{$conf->{pending
}});
940 my $running = PVE
::QemuServer
::check_running
($vmid);
942 # apply pending changes
944 $conf = PVE
::QemuServer
::load_config
($vmid); # update/reload
948 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
949 raise_param_exc
($errors) if scalar(keys %$errors);
951 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
961 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
963 if ($background_delay) {
965 # Note: It would be better to do that in the Event based HTTPServer
966 # to avoid blocking call to sleep.
968 my $end_time = time() + $background_delay;
970 my $task = PVE
::Tools
::upid_decode
($upid);
973 while (time() < $end_time) {
974 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
976 sleep(1); # this gets interrupted when child process ends
980 my $status = PVE
::Tools
::upid_read_status
($upid);
981 return undef if $status eq 'OK';
990 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
993 my $vm_config_perm_list = [
1000 'VM.Config.Options',
1003 __PACKAGE__-
>register_method({
1004 name
=> 'update_vm_async',
1005 path
=> '{vmid}/config',
1009 description
=> "Set virtual machine options (asynchrounous API).",
1011 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1014 additionalProperties
=> 0,
1015 properties
=> PVE
::QemuServer
::json_config_properties
(
1017 node
=> get_standard_option
('pve-node'),
1018 vmid
=> get_standard_option
('pve-vmid'),
1019 skiplock
=> get_standard_option
('skiplock'),
1021 type
=> 'string', format
=> 'pve-configid-list',
1022 description
=> "A list of settings you want to delete.",
1026 type
=> 'string', format
=> 'pve-configid-list',
1027 description
=> "Revert a pending change.",
1032 description
=> $opt_force_description,
1034 requires
=> 'delete',
1038 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1042 background_delay
=> {
1044 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1055 code
=> $update_vm_api,
1058 __PACKAGE__-
>register_method({
1059 name
=> 'update_vm',
1060 path
=> '{vmid}/config',
1064 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1066 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1069 additionalProperties
=> 0,
1070 properties
=> PVE
::QemuServer
::json_config_properties
(
1072 node
=> get_standard_option
('pve-node'),
1073 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1074 skiplock
=> get_standard_option
('skiplock'),
1076 type
=> 'string', format
=> 'pve-configid-list',
1077 description
=> "A list of settings you want to delete.",
1081 type
=> 'string', format
=> 'pve-configid-list',
1082 description
=> "Revert a pending change.",
1087 description
=> $opt_force_description,
1089 requires
=> 'delete',
1093 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1099 returns
=> { type
=> 'null' },
1102 &$update_vm_api($param, 1);
1108 __PACKAGE__-
>register_method({
1109 name
=> 'destroy_vm',
1114 description
=> "Destroy the vm (also delete all used/owned volumes).",
1116 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1119 additionalProperties
=> 0,
1121 node
=> get_standard_option
('pve-node'),
1122 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1123 skiplock
=> get_standard_option
('skiplock'),
1132 my $rpcenv = PVE
::RPCEnvironment
::get
();
1134 my $authuser = $rpcenv->get_user();
1136 my $vmid = $param->{vmid
};
1138 my $skiplock = $param->{skiplock
};
1139 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1140 if $skiplock && $authuser ne 'root@pam';
1143 my $conf = PVE
::QemuServer
::load_config
($vmid);
1145 my $storecfg = PVE
::Storage
::config
();
1147 die "can't remove VM $vmid - protection mode enabled\n"
1148 if ($conf->{protection
} == 1);
1150 die "unable to remove VM $vmid - used in HA resources\n"
1151 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1156 syslog
('info', "destroy VM $vmid: $upid\n");
1158 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1160 PVE
::AccessControl
::remove_vm_access
($vmid);
1162 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1165 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1168 __PACKAGE__-
>register_method({
1170 path
=> '{vmid}/unlink',
1174 description
=> "Unlink/delete disk images.",
1176 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1179 additionalProperties
=> 0,
1181 node
=> get_standard_option
('pve-node'),
1182 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1184 type
=> 'string', format
=> 'pve-configid-list',
1185 description
=> "A list of disk IDs you want to delete.",
1189 description
=> $opt_force_description,
1194 returns
=> { type
=> 'null'},
1198 $param->{delete} = extract_param
($param, 'idlist');
1200 __PACKAGE__-
>update_vm($param);
1207 __PACKAGE__-
>register_method({
1209 path
=> '{vmid}/vncproxy',
1213 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1215 description
=> "Creates a TCP VNC proxy connections.",
1217 additionalProperties
=> 0,
1219 node
=> get_standard_option
('pve-node'),
1220 vmid
=> get_standard_option
('pve-vmid'),
1224 description
=> "starts websockify instead of vncproxy",
1229 additionalProperties
=> 0,
1231 user
=> { type
=> 'string' },
1232 ticket
=> { type
=> 'string' },
1233 cert
=> { type
=> 'string' },
1234 port
=> { type
=> 'integer' },
1235 upid
=> { type
=> 'string' },
1241 my $rpcenv = PVE
::RPCEnvironment
::get
();
1243 my $authuser = $rpcenv->get_user();
1245 my $vmid = $param->{vmid
};
1246 my $node = $param->{node
};
1247 my $websocket = $param->{websocket
};
1249 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # check if VM exists
1251 my $authpath = "/vms/$vmid";
1253 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1255 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1258 my ($remip, $family);
1261 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1262 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1263 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1264 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
1266 $family = PVE
::Tools
::get_host_address_family
($node);
1269 my $port = PVE
::Tools
::next_vnc_port
($family);
1276 syslog
('info', "starting vnc proxy $upid\n");
1280 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1282 die "Websocket mode is not supported in vga serial mode!" if $websocket;
1284 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
} ];
1285 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1286 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1287 '-timeout', $timeout, '-authpath', $authpath,
1288 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
1291 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1293 my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1295 my $qmstr = join(' ', @$qmcmd);
1297 # also redirect stderr (else we get RFB protocol errors)
1298 $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"];
1301 PVE
::Tools
::run_command
($cmd);
1306 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
1308 PVE
::Tools
::wait_for_vnc_port
($port);
1319 __PACKAGE__-
>register_method({
1320 name
=> 'vncwebsocket',
1321 path
=> '{vmid}/vncwebsocket',
1324 description
=> "You also need to pass a valid ticket (vncticket).",
1325 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1327 description
=> "Opens a weksocket for VNC traffic.",
1329 additionalProperties
=> 0,
1331 node
=> get_standard_option
('pve-node'),
1332 vmid
=> get_standard_option
('pve-vmid'),
1334 description
=> "Ticket from previous call to vncproxy.",
1339 description
=> "Port number returned by previous vncproxy call.",
1349 port
=> { type
=> 'string' },
1355 my $rpcenv = PVE
::RPCEnvironment
::get
();
1357 my $authuser = $rpcenv->get_user();
1359 my $vmid = $param->{vmid
};
1360 my $node = $param->{node
};
1362 my $authpath = "/vms/$vmid";
1364 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1366 my $conf = PVE
::QemuServer
::load_config
($vmid, $node); # VM exists ?
1368 # Note: VNC ports are acessible from outside, so we do not gain any
1369 # security if we verify that $param->{port} belongs to VM $vmid. This
1370 # check is done by verifying the VNC ticket (inside VNC protocol).
1372 my $port = $param->{port
};
1374 return { port
=> $port };
1377 __PACKAGE__-
>register_method({
1378 name
=> 'spiceproxy',
1379 path
=> '{vmid}/spiceproxy',
1384 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1386 description
=> "Returns a SPICE configuration to connect to the VM.",
1388 additionalProperties
=> 0,
1390 node
=> get_standard_option
('pve-node'),
1391 vmid
=> get_standard_option
('pve-vmid'),
1392 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1395 returns
=> get_standard_option
('remote-viewer-config'),
1399 my $rpcenv = PVE
::RPCEnvironment
::get
();
1401 my $authuser = $rpcenv->get_user();
1403 my $vmid = $param->{vmid
};
1404 my $node = $param->{node
};
1405 my $proxy = $param->{proxy
};
1407 my $conf = PVE
::QemuServer
::load_config
($vmid, $node);
1408 my $title = "VM $vmid";
1409 $title .= " - ". $conf->{name
} if $conf->{name
};
1411 my $port = PVE
::QemuServer
::spice_port
($vmid);
1413 my ($ticket, undef, $remote_viewer_config) =
1414 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1416 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1417 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1419 return $remote_viewer_config;
1422 __PACKAGE__-
>register_method({
1424 path
=> '{vmid}/status',
1427 description
=> "Directory index",
1432 additionalProperties
=> 0,
1434 node
=> get_standard_option
('pve-node'),
1435 vmid
=> get_standard_option
('pve-vmid'),
1443 subdir
=> { type
=> 'string' },
1446 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1452 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1455 { subdir
=> 'current' },
1456 { subdir
=> 'start' },
1457 { subdir
=> 'stop' },
1463 __PACKAGE__-
>register_method({
1464 name
=> 'vm_status',
1465 path
=> '{vmid}/status/current',
1468 protected
=> 1, # qemu pid files are only readable by root
1469 description
=> "Get virtual machine status.",
1471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1474 additionalProperties
=> 0,
1476 node
=> get_standard_option
('pve-node'),
1477 vmid
=> get_standard_option
('pve-vmid'),
1480 returns
=> { type
=> 'object' },
1485 my $conf = PVE
::QemuServer
::load_config
($param->{vmid
});
1487 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1488 my $status = $vmstatus->{$param->{vmid
}};
1490 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
});
1492 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1497 __PACKAGE__-
>register_method({
1499 path
=> '{vmid}/status/start',
1503 description
=> "Start virtual machine.",
1505 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1508 additionalProperties
=> 0,
1510 node
=> get_standard_option
('pve-node'),
1511 vmid
=> get_standard_option
('pve-vmid'),
1512 skiplock
=> get_standard_option
('skiplock'),
1513 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1514 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1515 machine
=> get_standard_option
('pve-qm-machine'),
1524 my $rpcenv = PVE
::RPCEnvironment
::get
();
1526 my $authuser = $rpcenv->get_user();
1528 my $node = extract_param
($param, 'node');
1530 my $vmid = extract_param
($param, 'vmid');
1532 my $machine = extract_param
($param, 'machine');
1534 my $stateuri = extract_param
($param, 'stateuri');
1535 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1536 if $stateuri && $authuser ne 'root@pam';
1538 my $skiplock = extract_param
($param, 'skiplock');
1539 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1540 if $skiplock && $authuser ne 'root@pam';
1542 my $migratedfrom = extract_param
($param, 'migratedfrom');
1543 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1544 if $migratedfrom && $authuser ne 'root@pam';
1546 # read spice ticket from STDIN
1548 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1549 if (defined(my $line = <>)) {
1551 $spice_ticket = $line;
1555 my $storecfg = PVE
::Storage
::config
();
1557 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1558 $rpcenv->{type
} ne 'ha') {
1563 my $service = "vm:$vmid";
1565 my $cmd = ['ha-manager', 'enable', $service];
1567 print "Executing HA start for VM $vmid\n";
1569 PVE
::Tools
::run_command
($cmd);
1574 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1581 syslog
('info', "start VM $vmid: $upid\n");
1583 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
1584 $machine, $spice_ticket);
1589 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1593 __PACKAGE__-
>register_method({
1595 path
=> '{vmid}/status/stop',
1599 description
=> "Stop virtual machine.",
1601 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1604 additionalProperties
=> 0,
1606 node
=> get_standard_option
('pve-node'),
1607 vmid
=> get_standard_option
('pve-vmid'),
1608 skiplock
=> get_standard_option
('skiplock'),
1609 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
1611 description
=> "Wait maximal timeout seconds.",
1617 description
=> "Do not decativate storage volumes.",
1630 my $rpcenv = PVE
::RPCEnvironment
::get
();
1632 my $authuser = $rpcenv->get_user();
1634 my $node = extract_param
($param, 'node');
1636 my $vmid = extract_param
($param, 'vmid');
1638 my $skiplock = extract_param
($param, 'skiplock');
1639 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1640 if $skiplock && $authuser ne 'root@pam';
1642 my $keepActive = extract_param
($param, 'keepActive');
1643 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1644 if $keepActive && $authuser ne 'root@pam';
1646 my $migratedfrom = extract_param
($param, 'migratedfrom');
1647 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1648 if $migratedfrom && $authuser ne 'root@pam';
1651 my $storecfg = PVE
::Storage
::config
();
1653 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
1658 my $service = "vm:$vmid";
1660 my $cmd = ['ha-manager', 'disable', $service];
1662 print "Executing HA stop for VM $vmid\n";
1664 PVE
::Tools
::run_command
($cmd);
1669 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1675 syslog
('info', "stop VM $vmid: $upid\n");
1677 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
1678 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
1683 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1687 __PACKAGE__-
>register_method({
1689 path
=> '{vmid}/status/reset',
1693 description
=> "Reset virtual machine.",
1695 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1698 additionalProperties
=> 0,
1700 node
=> get_standard_option
('pve-node'),
1701 vmid
=> get_standard_option
('pve-vmid'),
1702 skiplock
=> get_standard_option
('skiplock'),
1711 my $rpcenv = PVE
::RPCEnvironment
::get
();
1713 my $authuser = $rpcenv->get_user();
1715 my $node = extract_param
($param, 'node');
1717 my $vmid = extract_param
($param, 'vmid');
1719 my $skiplock = extract_param
($param, 'skiplock');
1720 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1721 if $skiplock && $authuser ne 'root@pam';
1723 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1728 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
1733 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
1736 __PACKAGE__-
>register_method({
1737 name
=> 'vm_shutdown',
1738 path
=> '{vmid}/status/shutdown',
1742 description
=> "Shutdown virtual machine.",
1744 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1747 additionalProperties
=> 0,
1749 node
=> get_standard_option
('pve-node'),
1750 vmid
=> get_standard_option
('pve-vmid'),
1751 skiplock
=> get_standard_option
('skiplock'),
1753 description
=> "Wait maximal timeout seconds.",
1759 description
=> "Make sure the VM stops.",
1765 description
=> "Do not decativate storage volumes.",
1778 my $rpcenv = PVE
::RPCEnvironment
::get
();
1780 my $authuser = $rpcenv->get_user();
1782 my $node = extract_param
($param, 'node');
1784 my $vmid = extract_param
($param, 'vmid');
1786 my $skiplock = extract_param
($param, 'skiplock');
1787 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1788 if $skiplock && $authuser ne 'root@pam';
1790 my $keepActive = extract_param
($param, 'keepActive');
1791 raise_param_exc
({ keepActive
=> "Only root may use this option." })
1792 if $keepActive && $authuser ne 'root@pam';
1794 my $storecfg = PVE
::Storage
::config
();
1799 syslog
('info', "shutdown VM $vmid: $upid\n");
1801 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
1802 1, $param->{forceStop
}, $keepActive);
1807 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
1810 __PACKAGE__-
>register_method({
1811 name
=> 'vm_suspend',
1812 path
=> '{vmid}/status/suspend',
1816 description
=> "Suspend virtual machine.",
1818 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1821 additionalProperties
=> 0,
1823 node
=> get_standard_option
('pve-node'),
1824 vmid
=> get_standard_option
('pve-vmid'),
1825 skiplock
=> get_standard_option
('skiplock'),
1834 my $rpcenv = PVE
::RPCEnvironment
::get
();
1836 my $authuser = $rpcenv->get_user();
1838 my $node = extract_param
($param, 'node');
1840 my $vmid = extract_param
($param, 'vmid');
1842 my $skiplock = extract_param
($param, 'skiplock');
1843 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1844 if $skiplock && $authuser ne 'root@pam';
1846 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1851 syslog
('info', "suspend VM $vmid: $upid\n");
1853 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
1858 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
1861 __PACKAGE__-
>register_method({
1862 name
=> 'vm_resume',
1863 path
=> '{vmid}/status/resume',
1867 description
=> "Resume virtual machine.",
1869 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1872 additionalProperties
=> 0,
1874 node
=> get_standard_option
('pve-node'),
1875 vmid
=> get_standard_option
('pve-vmid'),
1876 skiplock
=> get_standard_option
('skiplock'),
1885 my $rpcenv = PVE
::RPCEnvironment
::get
();
1887 my $authuser = $rpcenv->get_user();
1889 my $node = extract_param
($param, 'node');
1891 my $vmid = extract_param
($param, 'vmid');
1893 my $skiplock = extract_param
($param, 'skiplock');
1894 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1895 if $skiplock && $authuser ne 'root@pam';
1897 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
1902 syslog
('info', "resume VM $vmid: $upid\n");
1904 PVE
::QemuServer
::vm_resume
($vmid, $skiplock);
1909 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
1912 __PACKAGE__-
>register_method({
1913 name
=> 'vm_sendkey',
1914 path
=> '{vmid}/sendkey',
1918 description
=> "Send key event to virtual machine.",
1920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1923 additionalProperties
=> 0,
1925 node
=> get_standard_option
('pve-node'),
1926 vmid
=> get_standard_option
('pve-vmid'),
1927 skiplock
=> get_standard_option
('skiplock'),
1929 description
=> "The key (qemu monitor encoding).",
1934 returns
=> { type
=> 'null'},
1938 my $rpcenv = PVE
::RPCEnvironment
::get
();
1940 my $authuser = $rpcenv->get_user();
1942 my $node = extract_param
($param, 'node');
1944 my $vmid = extract_param
($param, 'vmid');
1946 my $skiplock = extract_param
($param, 'skiplock');
1947 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1948 if $skiplock && $authuser ne 'root@pam';
1950 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
1955 __PACKAGE__-
>register_method({
1956 name
=> 'vm_feature',
1957 path
=> '{vmid}/feature',
1961 description
=> "Check if feature for virtual machine is available.",
1963 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1966 additionalProperties
=> 0,
1968 node
=> get_standard_option
('pve-node'),
1969 vmid
=> get_standard_option
('pve-vmid'),
1971 description
=> "Feature to check.",
1973 enum
=> [ 'snapshot', 'clone', 'copy' ],
1975 snapname
=> get_standard_option
('pve-snapshot-name', {
1983 hasFeature
=> { type
=> 'boolean' },
1986 items
=> { type
=> 'string' },
1993 my $node = extract_param
($param, 'node');
1995 my $vmid = extract_param
($param, 'vmid');
1997 my $snapname = extract_param
($param, 'snapname');
1999 my $feature = extract_param
($param, 'feature');
2001 my $running = PVE
::QemuServer
::check_running
($vmid);
2003 my $conf = PVE
::QemuServer
::load_config
($vmid);
2006 my $snap = $conf->{snapshots
}->{$snapname};
2007 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2010 my $storecfg = PVE
::Storage
::config
();
2012 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2013 my $hasFeature = PVE
::QemuServer
::has_feature
($feature, $conf, $storecfg, $snapname, $running);
2016 hasFeature
=> $hasFeature,
2017 nodes
=> [ keys %$nodelist ],
2021 __PACKAGE__-
>register_method({
2023 path
=> '{vmid}/clone',
2027 description
=> "Create a copy of virtual machine/template.",
2029 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2030 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2031 "'Datastore.AllocateSpace' on any used storage.",
2034 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2036 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2037 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2042 additionalProperties
=> 0,
2044 node
=> get_standard_option
('pve-node'),
2045 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2046 newid
=> get_standard_option
('pve-vmid', { description
=> 'VMID for the clone.' }),
2049 type
=> 'string', format
=> 'dns-name',
2050 description
=> "Set a name for the new VM.",
2055 description
=> "Description for the new VM.",
2059 type
=> 'string', format
=> 'pve-poolid',
2060 description
=> "Add the new VM to the specified pool.",
2062 snapname
=> get_standard_option
('pve-snapshot-name', {
2065 storage
=> get_standard_option
('pve-storage-id', {
2066 description
=> "Target storage for full clone.",
2071 description
=> "Target format for file storage.",
2075 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2080 description
=> "Create a full copy of all disk. This is always done when " .
2081 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2084 target
=> get_standard_option
('pve-node', {
2085 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2096 my $rpcenv = PVE
::RPCEnvironment
::get
();
2098 my $authuser = $rpcenv->get_user();
2100 my $node = extract_param
($param, 'node');
2102 my $vmid = extract_param
($param, 'vmid');
2104 my $newid = extract_param
($param, 'newid');
2106 my $pool = extract_param
($param, 'pool');
2108 if (defined($pool)) {
2109 $rpcenv->check_pool_exist($pool);
2112 my $snapname = extract_param
($param, 'snapname');
2114 my $storage = extract_param
($param, 'storage');
2116 my $format = extract_param
($param, 'format');
2118 my $target = extract_param
($param, 'target');
2120 my $localnode = PVE
::INotify
::nodename
();
2122 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2124 PVE
::Cluster
::check_node_exists
($target) if $target;
2126 my $storecfg = PVE
::Storage
::config
();
2129 # check if storage is enabled on local node
2130 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2132 # check if storage is available on target node
2133 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2134 # clone only works if target storage is shared
2135 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2136 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2140 PVE
::Cluster
::check_cfs_quorum
();
2142 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2144 # exclusive lock if VM is running - else shared lock is enough;
2145 my $shared_lock = $running ?
0 : 1;
2149 # do all tests after lock
2150 # we also try to do all tests before we fork the worker
2152 my $conf = PVE
::QemuServer
::load_config
($vmid);
2154 PVE
::QemuServer
::check_lock
($conf);
2156 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2158 die "unexpected state change\n" if $verify_running != $running;
2160 die "snapshot '$snapname' does not exist\n"
2161 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2163 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2165 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2167 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2169 my $conffile = PVE
::QemuServer
::config_file
($newid);
2171 die "unable to create VM $newid: config file already exists\n"
2174 my $newconf = { lock => 'clone' };
2178 foreach my $opt (keys %$oldconf) {
2179 my $value = $oldconf->{$opt};
2181 # do not copy snapshot related info
2182 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2183 $opt eq 'vmstate' || $opt eq 'snapstate';
2185 # no need to copy unused images, because VMID(owner) changes anyways
2186 next if $opt =~ m/^unused\d+$/;
2188 # always change MAC! address
2189 if ($opt =~ m/^net(\d+)$/) {
2190 my $net = PVE
::QemuServer
::parse_net
($value);
2191 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
();
2192 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2193 } elsif (PVE
::QemuServer
::valid_drivename
($opt)) {
2194 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2195 die "unable to parse drive options for '$opt'\n" if !$drive;
2196 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
2197 $newconf->{$opt} = $value; # simply copy configuration
2199 if ($param->{full
}) {
2200 die "Full clone feature is not available"
2201 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2204 # not full means clone instead of copy
2205 die "Linked clone feature is not available"
2206 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2208 $drives->{$opt} = $drive;
2209 push @$vollist, $drive->{file
};
2212 # copy everything else
2213 $newconf->{$opt} = $value;
2217 # auto generate a new uuid
2218 my ($uuid, $uuid_str);
2219 UUID
::generate
($uuid);
2220 UUID
::unparse
($uuid, $uuid_str);
2221 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2222 $smbios1->{uuid
} = $uuid_str;
2223 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2225 delete $newconf->{template
};
2227 if ($param->{name
}) {
2228 $newconf->{name
} = $param->{name
};
2230 if ($oldconf->{name
}) {
2231 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2233 $newconf->{name
} = "Copy-of-VM-$vmid";
2237 if ($param->{description
}) {
2238 $newconf->{description
} = $param->{description
};
2241 # create empty/temp config - this fails if VM already exists on other node
2242 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2247 my $newvollist = [];
2250 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2252 PVE
::Storage
::activate_volumes
($storecfg, $vollist);
2254 foreach my $opt (keys %$drives) {
2255 my $drive = $drives->{$opt};
2257 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2258 $newid, $storage, $format, $drive->{full
}, $newvollist);
2260 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2262 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2265 delete $newconf->{lock};
2266 PVE
::QemuServer
::update_config_nolock
($newid, $newconf, 1);
2269 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2270 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist);
2272 my $newconffile = PVE
::QemuServer
::config_file
($newid, $target);
2273 die "Failed to move config to node '$target' - rename failed: $!\n"
2274 if !rename($conffile, $newconffile);
2277 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2282 sleep 1; # some storage like rbd need to wait before release volume - really?
2284 foreach my $volid (@$newvollist) {
2285 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2288 die "clone failed: $err";
2294 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2296 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2299 return PVE
::QemuServer
::lock_config_mode
($vmid, 1, $shared_lock, sub {
2300 # Aquire exclusive lock lock for $newid
2301 return PVE
::QemuServer
::lock_config_full
($newid, 1, $clonefn);
2306 __PACKAGE__-
>register_method({
2307 name
=> 'move_vm_disk',
2308 path
=> '{vmid}/move_disk',
2312 description
=> "Move volume to different storage.",
2314 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2315 "and 'Datastore.AllocateSpace' permissions on the storage.",
2318 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2319 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2323 additionalProperties
=> 0,
2325 node
=> get_standard_option
('pve-node'),
2326 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2329 description
=> "The disk you want to move.",
2330 enum
=> [ PVE
::QemuServer
::disknames
() ],
2332 storage
=> get_standard_option
('pve-storage-id', {
2333 description
=> "Target storage.",
2334 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2338 description
=> "Target Format.",
2339 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2344 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2350 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2358 description
=> "the task ID.",
2363 my $rpcenv = PVE
::RPCEnvironment
::get
();
2365 my $authuser = $rpcenv->get_user();
2367 my $node = extract_param
($param, 'node');
2369 my $vmid = extract_param
($param, 'vmid');
2371 my $digest = extract_param
($param, 'digest');
2373 my $disk = extract_param
($param, 'disk');
2375 my $storeid = extract_param
($param, 'storage');
2377 my $format = extract_param
($param, 'format');
2379 my $storecfg = PVE
::Storage
::config
();
2381 my $updatefn = sub {
2383 my $conf = PVE
::QemuServer
::load_config
($vmid);
2385 die "checksum missmatch (file change by other user?)\n"
2386 if $digest && $digest ne $conf->{digest
};
2388 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2390 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2392 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2394 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2397 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2398 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2402 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2403 (!$format || !$oldfmt || $oldfmt eq $format);
2405 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2407 my $running = PVE
::QemuServer
::check_running
($vmid);
2409 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2413 my $newvollist = [];
2416 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2418 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2419 $vmid, $storeid, $format, 1, $newvollist);
2421 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2423 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid) if !$param->{delete};
2425 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2428 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2429 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2436 foreach my $volid (@$newvollist) {
2437 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2440 die "storage migration failed: $err";
2443 if ($param->{delete}) {
2444 my $used_paths = PVE
::QemuServer
::get_used_paths
($vmid, $storecfg, $conf, 1, 1);
2445 my $path = PVE
::Storage
::path
($storecfg, $old_volid);
2446 if ($used_paths->{$path}){
2447 warn "volume $old_volid have snapshots. Can't delete it\n";
2448 PVE
::QemuServer
::add_unused_volume
($conf, $old_volid);
2449 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2451 eval { PVE
::Storage
::vdisk_free
($storecfg, $old_volid); };
2457 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2460 return PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2463 __PACKAGE__-
>register_method({
2464 name
=> 'migrate_vm',
2465 path
=> '{vmid}/migrate',
2469 description
=> "Migrate virtual machine. Creates a new migration task.",
2471 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2474 additionalProperties
=> 0,
2476 node
=> get_standard_option
('pve-node'),
2477 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2478 target
=> get_standard_option
('pve-node', {
2479 description
=> "Target node.",
2480 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
2484 description
=> "Use online/live migration.",
2489 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
2496 description
=> "the task ID.",
2501 my $rpcenv = PVE
::RPCEnvironment
::get
();
2503 my $authuser = $rpcenv->get_user();
2505 my $target = extract_param
($param, 'target');
2507 my $localnode = PVE
::INotify
::nodename
();
2508 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
2510 PVE
::Cluster
::check_cfs_quorum
();
2512 PVE
::Cluster
::check_node_exists
($target);
2514 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
2516 my $vmid = extract_param
($param, 'vmid');
2518 raise_param_exc
({ force
=> "Only root may use this option." })
2519 if $param->{force
} && $authuser ne 'root@pam';
2522 my $conf = PVE
::QemuServer
::load_config
($vmid);
2524 # try to detect errors early
2526 PVE
::QemuServer
::check_lock
($conf);
2528 if (PVE
::QemuServer
::check_running
($vmid)) {
2529 die "cant migrate running VM without --online\n"
2530 if !$param->{online
};
2533 my $storecfg = PVE
::Storage
::config
();
2534 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
2536 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2541 my $service = "vm:$vmid";
2543 my $cmd = ['ha-manager', 'migrate', $service, $target];
2545 print "Executing HA migrate for VM $vmid to node $target\n";
2547 PVE
::Tools
::run_command
($cmd);
2552 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2559 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
2562 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2567 __PACKAGE__-
>register_method({
2569 path
=> '{vmid}/monitor',
2573 description
=> "Execute Qemu monitor commands.",
2575 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2578 additionalProperties
=> 0,
2580 node
=> get_standard_option
('pve-node'),
2581 vmid
=> get_standard_option
('pve-vmid'),
2584 description
=> "The monitor command.",
2588 returns
=> { type
=> 'string'},
2592 my $vmid = $param->{vmid
};
2594 my $conf = PVE
::QemuServer
::load_config
($vmid); # check if VM exists
2598 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
2600 $res = "ERROR: $@" if $@;
2605 __PACKAGE__-
>register_method({
2606 name
=> 'resize_vm',
2607 path
=> '{vmid}/resize',
2611 description
=> "Extend volume size.",
2613 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2616 additionalProperties
=> 0,
2618 node
=> get_standard_option
('pve-node'),
2619 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2620 skiplock
=> get_standard_option
('skiplock'),
2623 description
=> "The disk you want to resize.",
2624 enum
=> [PVE
::QemuServer
::disknames
()],
2628 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
2629 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.",
2633 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2639 returns
=> { type
=> 'null'},
2643 my $rpcenv = PVE
::RPCEnvironment
::get
();
2645 my $authuser = $rpcenv->get_user();
2647 my $node = extract_param
($param, 'node');
2649 my $vmid = extract_param
($param, 'vmid');
2651 my $digest = extract_param
($param, 'digest');
2653 my $disk = extract_param
($param, 'disk');
2655 my $sizestr = extract_param
($param, 'size');
2657 my $skiplock = extract_param
($param, 'skiplock');
2658 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2659 if $skiplock && $authuser ne 'root@pam';
2661 my $storecfg = PVE
::Storage
::config
();
2663 my $updatefn = sub {
2665 my $conf = PVE
::QemuServer
::load_config
($vmid);
2667 die "checksum missmatch (file change by other user?)\n"
2668 if $digest && $digest ne $conf->{digest
};
2669 PVE
::QemuServer
::check_lock
($conf) if !$skiplock;
2671 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2673 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2675 my (undef, undef, undef, undef, undef, undef, $format) =
2676 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
2678 die "can't resize volume: $disk if snapshot exists\n"
2679 if %{$conf->{snapshots
}} && $format eq 'qcow2';
2681 my $volid = $drive->{file
};
2683 die "disk '$disk' has no associated volume\n" if !$volid;
2685 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
2687 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
2689 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2691 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
2693 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2694 my ($ext, $newsize, $unit) = ($1, $2, $4);
2697 $newsize = $newsize * 1024;
2698 } elsif ($unit eq 'M') {
2699 $newsize = $newsize * 1024 * 1024;
2700 } elsif ($unit eq 'G') {
2701 $newsize = $newsize * 1024 * 1024 * 1024;
2702 } elsif ($unit eq 'T') {
2703 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2706 $newsize += $size if $ext;
2707 $newsize = int($newsize);
2709 die "unable to skrink disk size\n" if $newsize < $size;
2711 return if $size == $newsize;
2713 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
2715 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
2717 $drive->{size
} = $newsize;
2718 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
2720 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2723 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2727 __PACKAGE__-
>register_method({
2728 name
=> 'snapshot_list',
2729 path
=> '{vmid}/snapshot',
2731 description
=> "List all snapshots.",
2733 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2736 protected
=> 1, # qemu pid files are only readable by root
2738 additionalProperties
=> 0,
2740 vmid
=> get_standard_option
('pve-vmid'),
2741 node
=> get_standard_option
('pve-node'),
2750 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
2755 my $vmid = $param->{vmid
};
2757 my $conf = PVE
::QemuServer
::load_config
($vmid);
2758 my $snaphash = $conf->{snapshots
} || {};
2762 foreach my $name (keys %$snaphash) {
2763 my $d = $snaphash->{$name};
2766 snaptime
=> $d->{snaptime
} || 0,
2767 vmstate
=> $d->{vmstate
} ?
1 : 0,
2768 description
=> $d->{description
} || '',
2770 $item->{parent
} = $d->{parent
} if $d->{parent
};
2771 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
2775 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2776 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
2777 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
2779 push @$res, $current;
2784 __PACKAGE__-
>register_method({
2786 path
=> '{vmid}/snapshot',
2790 description
=> "Snapshot a VM.",
2792 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2795 additionalProperties
=> 0,
2797 node
=> get_standard_option
('pve-node'),
2798 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2799 snapname
=> get_standard_option
('pve-snapshot-name'),
2803 description
=> "Save the vmstate",
2808 description
=> "A textual description or comment.",
2814 description
=> "the task ID.",
2819 my $rpcenv = PVE
::RPCEnvironment
::get
();
2821 my $authuser = $rpcenv->get_user();
2823 my $node = extract_param
($param, 'node');
2825 my $vmid = extract_param
($param, 'vmid');
2827 my $snapname = extract_param
($param, 'snapname');
2829 die "unable to use snapshot name 'current' (reserved name)\n"
2830 if $snapname eq 'current';
2833 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
2834 PVE
::QemuServer
::snapshot_create
($vmid, $snapname, $param->{vmstate
},
2835 $param->{description
});
2838 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2841 __PACKAGE__-
>register_method({
2842 name
=> 'snapshot_cmd_idx',
2843 path
=> '{vmid}/snapshot/{snapname}',
2850 additionalProperties
=> 0,
2852 vmid
=> get_standard_option
('pve-vmid'),
2853 node
=> get_standard_option
('pve-node'),
2854 snapname
=> get_standard_option
('pve-snapshot-name'),
2863 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
2870 push @$res, { cmd
=> 'rollback' };
2871 push @$res, { cmd
=> 'config' };
2876 __PACKAGE__-
>register_method({
2877 name
=> 'update_snapshot_config',
2878 path
=> '{vmid}/snapshot/{snapname}/config',
2882 description
=> "Update snapshot metadata.",
2884 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2887 additionalProperties
=> 0,
2889 node
=> get_standard_option
('pve-node'),
2890 vmid
=> get_standard_option
('pve-vmid'),
2891 snapname
=> get_standard_option
('pve-snapshot-name'),
2895 description
=> "A textual description or comment.",
2899 returns
=> { type
=> 'null' },
2903 my $rpcenv = PVE
::RPCEnvironment
::get
();
2905 my $authuser = $rpcenv->get_user();
2907 my $vmid = extract_param
($param, 'vmid');
2909 my $snapname = extract_param
($param, 'snapname');
2911 return undef if !defined($param->{description
});
2913 my $updatefn = sub {
2915 my $conf = PVE
::QemuServer
::load_config
($vmid);
2917 PVE
::QemuServer
::check_lock
($conf);
2919 my $snap = $conf->{snapshots
}->{$snapname};
2921 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2923 $snap->{description
} = $param->{description
} if defined($param->{description
});
2925 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
2928 PVE
::QemuServer
::lock_config
($vmid, $updatefn);
2933 __PACKAGE__-
>register_method({
2934 name
=> 'get_snapshot_config',
2935 path
=> '{vmid}/snapshot/{snapname}/config',
2938 description
=> "Get snapshot configuration",
2940 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2943 additionalProperties
=> 0,
2945 node
=> get_standard_option
('pve-node'),
2946 vmid
=> get_standard_option
('pve-vmid'),
2947 snapname
=> get_standard_option
('pve-snapshot-name'),
2950 returns
=> { type
=> "object" },
2954 my $rpcenv = PVE
::RPCEnvironment
::get
();
2956 my $authuser = $rpcenv->get_user();
2958 my $vmid = extract_param
($param, 'vmid');
2960 my $snapname = extract_param
($param, 'snapname');
2962 my $conf = PVE
::QemuServer
::load_config
($vmid);
2964 my $snap = $conf->{snapshots
}->{$snapname};
2966 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2971 __PACKAGE__-
>register_method({
2973 path
=> '{vmid}/snapshot/{snapname}/rollback',
2977 description
=> "Rollback VM state to specified snapshot.",
2979 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2982 additionalProperties
=> 0,
2984 node
=> get_standard_option
('pve-node'),
2985 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2986 snapname
=> get_standard_option
('pve-snapshot-name'),
2991 description
=> "the task ID.",
2996 my $rpcenv = PVE
::RPCEnvironment
::get
();
2998 my $authuser = $rpcenv->get_user();
3000 my $node = extract_param
($param, 'node');
3002 my $vmid = extract_param
($param, 'vmid');
3004 my $snapname = extract_param
($param, 'snapname');
3007 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3008 PVE
::QemuServer
::snapshot_rollback
($vmid, $snapname);
3011 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
3014 __PACKAGE__-
>register_method({
3015 name
=> 'delsnapshot',
3016 path
=> '{vmid}/snapshot/{snapname}',
3020 description
=> "Delete a VM snapshot.",
3022 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3025 additionalProperties
=> 0,
3027 node
=> get_standard_option
('pve-node'),
3028 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3029 snapname
=> get_standard_option
('pve-snapshot-name'),
3033 description
=> "For removal from config file, even if removing disk snapshots fails.",
3039 description
=> "the task ID.",
3044 my $rpcenv = PVE
::RPCEnvironment
::get
();
3046 my $authuser = $rpcenv->get_user();
3048 my $node = extract_param
($param, 'node');
3050 my $vmid = extract_param
($param, 'vmid');
3052 my $snapname = extract_param
($param, 'snapname');
3055 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3056 PVE
::QemuServer
::snapshot_delete
($vmid, $snapname, $param->{force
});
3059 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3062 __PACKAGE__-
>register_method({
3064 path
=> '{vmid}/template',
3068 description
=> "Create a Template.",
3070 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3071 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3074 additionalProperties
=> 0,
3076 node
=> get_standard_option
('pve-node'),
3077 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3081 description
=> "If you want to convert only 1 disk to base image.",
3082 enum
=> [PVE
::QemuServer
::disknames
()],
3087 returns
=> { type
=> 'null'},
3091 my $rpcenv = PVE
::RPCEnvironment
::get
();
3093 my $authuser = $rpcenv->get_user();
3095 my $node = extract_param
($param, 'node');
3097 my $vmid = extract_param
($param, 'vmid');
3099 my $disk = extract_param
($param, 'disk');
3101 my $updatefn = sub {
3103 my $conf = PVE
::QemuServer
::load_config
($vmid);
3105 PVE
::QemuServer
::check_lock
($conf);
3107 die "unable to create template, because VM contains snapshots\n"
3108 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3110 die "you can't convert a template to a template\n"
3111 if PVE
::QemuServer
::is_template
($conf) && !$disk;
3113 die "you can't convert a VM to template if VM is running\n"
3114 if PVE
::QemuServer
::check_running
($vmid);
3117 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3120 $conf->{template
} = 1;
3121 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
3123 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3126 PVE
::QemuServer
::lock_config
($vmid, $updatefn);