use UUID;
use POSIX;
use IO::Socket::IP;
+use URI::Escape;
use PVE::Cluster qw (cfs_read_file cfs_write_file);;
use PVE::SafeSyslog;
use PVE::Network;
use PVE::Firewall;
use PVE::API2::Firewall::VM;
+use PVE::API2::Qemu::Agent;
BEGIN {
if (!$ENV{PVE_GENERATING_DOCS}) {
my $volid = $drive->{file};
- if (!$volid || $volid eq 'none') {
+ if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
+ # nothing to check
+ } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
# nothing to check
} elsif ($isCDROM && ($volid eq 'cdrom')) {
$rpcenv->check($authuser, "/", ['Sys.Console']);
if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
delete $disk->{size};
$res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
+ } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
+ my $storeid = $1 || $default_storage;
+ die "no storage ID specified (and no default storage)\n" if !$storeid;
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ my $name = "vm-$vmid-cloudinit";
+ my $fmt = undef;
+ if ($scfg->{path}) {
+ $name .= ".qcow2";
+ $fmt = 'qcow2';
+ }else{
+ $fmt = 'raw';
+ }
+ # FIXME: Reasonable size? qcow2 shouldn't grow if the space isn't used anyway?
+ my $cloudinit_iso_size = 5; # in MB
+ my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid,
+ $fmt, $name, $cloudinit_iso_size*1024);
+ $disk->{file} = $volid;
+ $disk->{media} = 'cdrom';
+ push @$vollist, $volid;
+ delete $disk->{format}; # no longer needed
+ $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
} elsif ($volid =~ $NEW_DISK_RE) {
my ($storeid, $size) = ($2 || $default_storage, $3);
die "no storage ID specified (and no default storage)\n" if !$storeid;
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
} elsif ($diskoptions->{$opt}) {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
- } elsif ($opt =~ m/^net\d+$/) {
+ } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
} else {
# catches usb\d+, hostpci\d+, args, lock, etc.
type => 'string', format => 'pve-poolid',
description => "Add the VM to the specified pool.",
},
+ bwlimit => {
+ description => "Override i/o bandwidth limit (in KiB/s).",
+ optional => 1,
+ type => 'integer',
+ minimum => '0',
+ }
}),
},
returns => {
my $pool = extract_param($param, 'pool');
+ my $bwlimit = extract_param($param, 'bwlimit');
+
my $filename = PVE::QemuConfig->config_file($vmid);
my $storecfg = PVE::Storage::config();
+ if (defined(my $ssh_keys = $param->{sshkeys})) {
+ $ssh_keys = URI::Escape::uri_unescape($ssh_keys);
+ PVE::Tools::validate_ssh_public_keys($ssh_keys);
+ }
+
PVE::Cluster::check_cfs_quorum();
if (defined($pool)) {
PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
storage => $storage,
pool => $pool,
- unique => $unique });
+ unique => $unique,
+ bwlimit => $bwlimit, });
PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
};
path => '{vmid}/firewall',
});
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Qemu::Agent",
+ path => '{vmid}/agent',
+});
+
__PACKAGE__->register_method({
name => 'rrd',
path => '{vmid}/rrd',
delete $conf->{pending};
+ # hide cloudinit password
+ if ($conf->{cipassword}) {
+ $conf->{cipassword} = '**********';
+ }
+
return $conf;
}});
$item->{value} = $conf->{$opt} if defined($conf->{$opt});
$item->{pending} = $conf->{pending}->{$opt} if defined($conf->{pending}->{$opt});
$item->{delete} = ($pending_delete_hash->{$opt} ? 2 : 1) if exists $pending_delete_hash->{$opt};
+
+ # hide cloudinit password
+ if ($opt eq 'cipassword') {
+ $item->{value} = '**********' if defined($item->{value});
+ # the trailing space so that the pending string is different
+ $item->{pending} = '********** ' if defined($item->{pending});
+ }
push @$res, $item;
}
next if defined($conf->{$opt});
my $item = { key => $opt };
$item->{pending} = $conf->{pending}->{$opt};
+
+ # hide cloudinit password
+ if ($opt eq 'cipassword') {
+ $item->{pending} = '**********' if defined($item->{pending});
+ }
push @$res, $item;
}
my $background_delay = extract_param($param, 'background_delay');
+ if (defined(my $cipassword = $param->{cipassword})) {
+ # Same logic as in cloud-init (but with the regex fixed...)
+ $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
+ if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
+ }
+
my @paramarr = (); # used for log message
foreach my $key (sort keys %$param) {
- push @paramarr, "-$key", $param->{$key};
+ my $value = $key eq 'cipassword' ? '<hidden>' : $param->{$key};
+ push @paramarr, "-$key", $value;
}
my $skiplock = extract_param($param, 'skiplock');
my $force = extract_param($param, 'force');
+ if (defined(my $ssh_keys = $param->{sshkeys})) {
+ $ssh_keys = URI::Escape::uri_unescape($ssh_keys);
+ PVE::Tools::validate_ssh_public_keys($ssh_keys);
+ }
+
die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
my $storecfg = PVE::Storage::config();
if (PVE::QemuServer::is_valid_drivename($opt)) {
my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
+ # FIXME: cloudinit: CDROM or Disk?
if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM
$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
} else {
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
- newid => get_standard_option('pve-vmid', { description => 'VMID for the clone.' }),
+ newid => get_standard_option('pve-vmid', {
+ completion => \&PVE::Cluster::complete_next_vmid,
+ description => 'VMID for the clone.' }),
name => {
optional => 1,
type => 'string', format => 'dns-name',
}),
storage => get_standard_option('pve-storage-id', {
description => "Target storage for full clone.",
- requires => 'full',
optional => 1,
}),
'format' => {
- description => "Target format for file storage.",
- requires => 'full',
+ description => "Target format for file storage. Only valid for full clone.",
type => 'string',
optional => 1,
enum => [ 'raw', 'qcow2', 'vmdk'],
full => {
optional => 1,
type => 'boolean',
- description => "Create a full copy of all disk. This is always done when " .
+ description => "Create a full copy of all disks. This is always done when " .
"you clone a normal VM. For VM templates, we try to create a linked clone by default.",
- default => 0,
},
target => get_standard_option('pve-node', {
description => "Target node. Only allowed if the original VM is on shared storage.",
die "snapshot '$snapname' does not exist\n"
if $snapname && !defined( $conf->{snapshots}->{$snapname});
+ my $full = extract_param($param, 'full');
+ if (!defined($full)) {
+ $full = !PVE::QemuConfig->is_template($conf);
+ }
+
+ die "parameter 'storage' not allowed for linked clones\n"
+ if defined($storage) && !$full;
+
+ die "parameter 'format' not allowed for linked clones\n"
+ if defined($format) && !$full;
+
my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
} elsif (PVE::QemuServer::is_valid_drivename($opt)) {
my $drive = PVE::QemuServer::parse_drive($opt, $value);
die "unable to parse drive options for '$opt'\n" if !$drive;
- if (PVE::QemuServer::drive_is_cdrom($drive)) {
+ if (PVE::QemuServer::drive_is_cdrom($drive, 1)) {
$newconf->{$opt} = $value; # simply copy configuration
} else {
- if ($param->{full}) {
+ if ($full || PVE::QemuServer::drive_is_cloudinit($drive)) {
die "Full clone feature is not supported for drive '$opt'\n"
if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
$fullclone->{$opt} = 1;
my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
- die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
+ die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive, 1);
my $oldfmt;
my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
return $res;
}});
-my $guest_agent_commands = [
- 'ping',
- 'get-time',
- 'info',
- 'fsfreeze-status',
- 'fsfreeze-freeze',
- 'fsfreeze-thaw',
- 'fstrim',
- 'network-get-interfaces',
- 'get-vcpus',
- 'get-fsinfo',
- 'get-memory-blocks',
- 'get-memory-block-info',
- 'suspend-hybrid',
- 'suspend-ram',
- 'suspend-disk',
- 'shutdown',
- ];
-
-__PACKAGE__->register_method({
- name => 'agent',
- path => '{vmid}/agent',
- method => 'POST',
- protected => 1,
- proxyto => 'node',
- description => "Execute Qemu Guest Agent commands.",
- permissions => {
- check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node'),
- vmid => get_standard_option('pve-vmid', {
- completion => \&PVE::QemuServer::complete_vmid_running }),
- command => {
- type => 'string',
- description => "The QGA command.",
- enum => $guest_agent_commands,
- },
- },
- },
- returns => {
- type => 'object',
- description => "Returns an object with a single `result` property. The type of that
-property depends on the executed command.",
- },
- code => sub {
- my ($param) = @_;
-
- my $vmid = $param->{vmid};
-
- my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
-
- die "No Qemu Guest Agent\n" if !defined($conf->{agent});
- die "VM $vmid is not running\n" if !PVE::QemuServer::check_running($vmid);
-
- my $cmd = $param->{command};
-
- my $res = PVE::QemuServer::vm_mon_cmd($vmid, "guest-$cmd");
-
- return { result => $res };
- }});
-
__PACKAGE__->register_method({
name => 'resize_vm',
path => '{vmid}/resize',