use File::Copy qw(copy);
use URI::Escape;
-my $OVMF_CODE = '/usr/share/kvm/OVMF_CODE-pure-efi.fd';
-my $OVMF_VARS = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
+my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
+my $OVMF_CODE = "$EDK2_FW_BASE/OVMF_CODE.fd";
+my $OVMF_VARS = "$EDK2_FW_BASE/OVMF_VARS.fd";
my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
+my $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
+
# Note about locking: we use flock on the config file protect
# against concurent actions.
# Aditionaly, we have a 'lock' setting in the config file. This
description => "Select BIOS implementation.",
default => 'seabios',
},
+};
+
+my $confdesc_cloudinit = {
+ citype => {
+ optional => 1,
+ type => 'string',
+ description => 'Specifies the cloud-init configuration format. The default depends on the configured operating system type (`ostype`. We use the `nocloud` format for Linux, and `configdrive2` for windows.',
+ enum => ['configdrive2', 'nocloud'],
+ },
+ ciuser => {
+ optional => 1,
+ type => 'string',
+ description => "cloud-init: User name to change ssh keys and password for instead of the image's configured default user.",
+ },
+ cipassword => {
+ optional => 1,
+ type => 'string',
+ description => 'cloud-init: Password to assign the user. Using this is generally not recommended. Use ssh keys instead. Also note that older cloud-init versions do not support hashed passwords.',
+ },
searchdomain => {
optional => 1,
type => 'string',
optional => 1,
type => 'string',
format => 'urlencoded',
- description => "cloud-init : Setup public SSH keys (one key per line, " .
- "OpenSSH format).",
- },
- hostname => {
- optional => 1,
- description => "cloud-init: Hostname to use instead of the vm-name + search-domain.",
- type => 'string', format => 'dns-name',
- maxLength => 255,
+ description => "cloud-init: Setup public SSH keys (one key per line, OpenSSH format).",
},
};
for (my $i = 0; $i < $MAX_NETS; $i++) {
$confdesc->{"net$i"} = $netdesc;
- $confdesc->{"ipconfig$i"} = $ipconfigdesc;
+ $confdesc_cloudinit->{"ipconfig$i"} = $ipconfigdesc;
+}
+
+foreach my $key (keys %$confdesc_cloudinit) {
+ $confdesc->{$key} = $confdesc_cloudinit->{$key};
}
PVE::JSONSchema::register_format('pve-volume-id-or-qm-path', \&verify_volume_id_or_qm_path);
$device .= ",bootindex=$drive->{bootindex}" if $drive->{bootindex};
+ if (my $serial = $drive->{serial}) {
+ $serial = URI::Escape::uri_unescape($serial);
+ $device .= ",serial=$serial";
+ }
+
+
return $device;
}
}
}
- if (my $serial = $drive->{serial}) {
- $serial = URI::Escape::uri_unescape($serial);
- $opts .= ",serial=$serial";
- }
-
$opts .= ",format=$format" if $format && !$drive->{format};
my $cache_direct = 0;
return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
}
-sub drive_is_cdrom {
+sub drive_is_cloudinit {
my ($drive) = @_;
+ return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
+}
+
+sub drive_is_cdrom {
+ my ($drive, $exclude_cloudinit) = @_;
+
+ return 0 if $exclude_cloudinit && drive_is_cloudinit($drive);
return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');
my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
$res->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
}
- $res->{macaddr} = PVE::Tools::random_ether_addr() if !defined($res->{macaddr});
return $res;
}
sub vmconfig_register_unused_drive {
my ($storecfg, $vmid, $conf, $drive) = @_;
- if (!drive_is_cdrom($drive)) {
+ if (drive_is_cloudinit($drive)) {
+ eval { PVE::Storage::vdisk_free($storecfg, $drive->{file}) };
+ warn $@ if $@;
+ } elsif (!drive_is_cdrom($drive)) {
my $volid = $drive->{file};
if (vm_is_volid_owner($storecfg, $vmid, $volid)) {
PVE::QemuConfig->add_unused_volume($conf, $volid, $vmid);
return $prop;
}
+# return copy of $confdesc_cloudinit to generate documentation
+sub cloudinit_config_properties {
+
+ return dclone($confdesc_cloudinit);
+}
+
sub check_type {
my ($key, $value) = @_;
foreach_drive($conf, sub {
my ($ds, $drive) = @_;
- return if drive_is_cdrom($drive);
+ return if drive_is_cdrom($drive, 1);
my $volid = $drive->{file};
}
}
- my $conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
- $res->{keyboard} = $conf->{keyboard} if $conf->{keyboard};
-
return $res;
}
push @$cmd, '-id', $vmid;
+ my $vmname = $conf->{name} || "vm$vmid";
+
+ push @$cmd, '-name', $vmname;
+
my $use_virtio = 0;
my $qmpsocket = qmp_socket($vmid);
}
}
- my $vmname = $conf->{name} || "vm$vmid";
-
- push @$cmd, '-name', $vmname;
my $sockets = 1;
$sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
push @$cmd, '-S' if $conf->{freeze};
- # set keyboard layout
- my $kb = $conf->{keyboard} || $defaults->{keyboard};
- push @$cmd, '-k', $kb if $kb;
+ push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard});
# enable sound
#my $soundhw = $conf->{soundhw} || $defaults->{soundhw};
return $res;
}
-# old code, only used to shutdown old VM after update
-sub vm_monitor_command {
- my ($vmid, $cmdstr, $nocheck) = @_;
-
- my $res;
-
- eval {
- die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
-
- my $sname = "${var_run_tmpdir}/$vmid.mon";
-
- my $sock = IO::Socket::UNIX->new( Peer => $sname ) ||
- die "unable to connect to VM $vmid socket - $!\n";
-
- my $timeout = 3;
-
- # hack: migrate sometime blocks the monitor (when migrate_downtime
- # is set)
- if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) {
- $timeout = 60*60; # 1 hour
- }
-
- # read banner;
- my $data = __read_avail($sock, $timeout);
-
- if ($data !~ m/^QEMU\s+(\S+)\s+monitor\s/) {
- die "got unexpected qemu monitor banner\n";
- }
-
- my $sel = new IO::Select;
- $sel->add($sock);
-
- if (!scalar(my @ready = $sel->can_write($timeout))) {
- die "monitor write error - timeout";
- }
-
- my $fullcmd = "$cmdstr\r";
-
- # syslog('info', "VM $vmid monitor command: $cmdstr");
-
- my $b;
- if (!($b = $sock->syswrite($fullcmd)) || ($b != length($fullcmd))) {
- die "monitor write error - $!";
- }
-
- return if ($cmdstr eq 'q') || ($cmdstr eq 'quit');
-
- $timeout = 20;
-
- if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) {
- $timeout = 60*60; # 1 hour
- } elsif ($cmdstr =~ m/^(eject|change)/) {
- $timeout = 60; # note: cdrom mount command is slow
- }
- if ($res = __read_avail($sock, $timeout)) {
-
- my @lines = split("\r?\n", $res);
-
- shift @lines if $lines[0] !~ m/^unknown command/; # skip echo
-
- $res = join("\n", @lines);
- $res .= "\n";
- }
- };
-
- my $err = $@;
-
- if ($err) {
- syslog("err", "VM $vmid monitor command failed - $err");
- die $err;
- }
-
- return $res;
-}
-
sub qemu_block_resize {
my ($vmid, $deviceid, $storecfg, $volid, $size) = @_;
}
}
+ my $apply_pending_cloudinit;
+ $apply_pending_cloudinit = sub {
+ my ($key, $value) = @_;
+ $apply_pending_cloudinit = sub {}; # once is enough
+
+ my @cloudinit_opts = keys %$confdesc_cloudinit;
+ foreach my $opt (keys %{$conf->{pending}}) {
+ next if !grep { $_ eq $opt } @cloudinit_opts;
+ $conf->{$opt} = delete $conf->{pending}->{$opt};
+ }
+
+ my $new_conf = { %$conf };
+ $new_conf->{$key} = $value;
+ PVE::QemuServer::Cloudinit::generate_cloudinitconfig($new_conf, $vmid);
+ };
+
foreach my $opt (keys %{$conf->{pending}}) {
next if $selection && !$selection->{$opt};
my $value = $conf->{pending}->{$opt};
$vmid, $opt, $value);
} elsif (is_valid_drivename($opt)) {
# some changes can be done without hotplug
+ my $drive = parse_drive($opt, $value);
+ if (drive_is_cloudinit($drive)) {
+ &$apply_pending_cloudinit($opt, $value);
+ }
vmconfig_update_disk($storecfg, $conf, $hotplug_features->{disk},
$vmid, $opt, $value, 1);
} elsif ($opt =~ m/^memory$/) { #dimms
if ($drive->{file} eq 'none') {
vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt");
+ if (drive_is_cloudinit($old_drive)) {
+ vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive);
+ }
} else {
my $path = get_iso_path($storecfg, $vmid, $drive->{file});
vm_mon_cmd($vmid, "eject", force => JSON::true,device => "drive-$opt"); # force eject if locked
PVE::Storage::activate_volumes($storecfg, $vollist);
- if (!check_running($vmid, 1) && -d "/sys/fs/cgroup/systemd/qemu.slice/$vmid.scope") {
- my $cmd = [];
- push @$cmd, '/bin/systemctl', 'stop', "$vmid.scope";
- eval { run_command($cmd); };
+ if (!check_running($vmid, 1)) {
+ eval {
+ run_command(['/bin/systemctl', 'stop', "$vmid.scope"],
+ outfunc => sub {}, errfunc => sub {});
+ };
}
my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
my $qmpclient = PVE::QMPClient->new();
$res = $qmpclient->cmd($vmid, $cmd, $timeout);
- } elsif (-e "${var_run_tmpdir}/$vmid.mon") {
- die "can't execute complex command on old monitor - stop/start your vm to fix the problem\n"
- if scalar(%{$cmd->{arguments}});
- vm_monitor_command($vmid, $cmd->{execute}, $nocheck);
} else {
die "unable to open monitor socket\n";
}
sub restore_vma_archive {
my ($archive, $vmid, $user, $opts, $comp) = @_;
- my $input = $archive eq '-' ? "<&STDIN" : undef;
my $readfrom = $archive;
- my $uncomp = '';
- if ($comp) {
+ my $cfg = PVE::Storage::config();
+ my $commands = [];
+ my $bwlimit = $opts->{bwlimit};
+
+ my $dbg_cmdstring = '';
+ my $add_pipe = sub {
+ my ($cmd) = @_;
+ push @$commands, $cmd;
+ $dbg_cmdstring .= ' | ' if length($dbg_cmdstring);
+ $dbg_cmdstring .= PVE::Tools::cmd2string($cmd);
$readfrom = '-';
- my $qarchive = PVE::Tools::shellquote($archive);
+ };
+
+ my $input = undef;
+ if ($archive eq '-') {
+ $input = '<&STDIN';
+ } else {
+ # If we use a backup from a PVE defined storage we also consider that
+ # storage's rate limit:
+ my (undef, $volid) = PVE::Storage::path_to_volume_id($cfg, $archive);
+ if (defined($volid)) {
+ my ($sid, undef) = PVE::Storage::parse_volume_id($volid);
+ my $readlimit = PVE::Storage::get_bandwidth_limit('restore', [$sid], $bwlimit);
+ if ($readlimit) {
+ print STDERR "applying read rate limit: $readlimit\n";
+ my $cstream = ['cstream', '-t', $readlimit*1024, '--', $readfrom];
+ $add_pipe->($cstream);
+ }
+ }
+ }
+
+ if ($comp) {
+ my $cmd;
if ($comp eq 'gzip') {
- $uncomp = "zcat $qarchive|";
+ $cmd = ['zcat', $readfrom];
} elsif ($comp eq 'lzop') {
- $uncomp = "lzop -d -c $qarchive|";
+ $cmd = ['lzop', '-d', '-c', $readfrom];
} else {
die "unknown compression method '$comp'\n";
}
-
+ $add_pipe->($cmd);
}
my $tmpdir = "/var/tmp/vzdumptmp$$";
open($fifofh, '>', $mapfifo) || die $!;
};
- my $cmd = "${uncomp}vma extract -v -r $mapfifo $readfrom $tmpdir";
+ $add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]);
my $oldtimeout;
my $timeout = 5;
my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
+ my %storage_limits;
+
my $print_devmap = sub {
my $virtdev_hash = {};
$rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
}
+ $storage_limits{$storeid} = $bwlimit;
+
$virtdev_hash->{$virtdev} = $devinfo->{$devname};
}
}
+ foreach my $key (keys %storage_limits) {
+ my $limit = PVE::Storage::get_bandwidth_limit('restore', [$key], $bwlimit);
+ next if !$limit;
+ print STDERR "rate limit for storage $key: $limit KiB/s\n";
+ $storage_limits{$key} = $limit * 1024;
+ }
+
foreach my $devname (keys %$devinfo) {
die "found no device mapping information for device '$devname'\n"
if !$devinfo->{$devname}->{virtdev};
}
- my $cfg = PVE::Storage::config();
-
# create empty/temp config
if ($oldconf) {
PVE::Tools::file_set_contents($conffile, "memory: 128\n");
foreach my $virtdev (sort keys %$virtdev_hash) {
my $d = $virtdev_hash->{$virtdev};
my $alloc_size = int(($d->{size} + 1024 - 1)/1024);
- my $scfg = PVE::Storage::storage_config($cfg, $d->{storeid});
+ my $storeid = $d->{storeid};
+ my $scfg = PVE::Storage::storage_config($cfg, $storeid);
+
+ my $map_opts = '';
+ if (my $limit = $storage_limits{$storeid}) {
+ $map_opts .= "throttling.bps=$limit:throttling.group=$storeid:";
+ }
# test if requested format is supported
- my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $d->{storeid});
+ my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $storeid);
my $supported = grep { $_ eq $d->{format} } @$validFormats;
$d->{format} = $defFormat if !$supported;
- my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid,
+ my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid,
$d->{format}, undef, $alloc_size);
print STDERR "new volume ID is '$volid'\n";
$d->{volid} = $volid;
$write_zeros = 0;
}
- print $fifofh "format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
+ print $fifofh "${map_opts}format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
$map->{$virtdev} = $volid;
}
};
- print "restore vma archive: $cmd\n";
- run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
+ print "restore vma archive: $dbg_cmdstring\n";
+ run_command($commands, input => $input, outfunc => $parser, afterfork => $openfifo);
};
my $err = $@;
push @$vollist, $volid if $volid;
}
- my $cfg = PVE::Storage::config();
PVE::Storage::deactivate_volumes($cfg, $vollist);
unlink $mapfifo;
my $cmd = [];
push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2");
+ push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
+ push @$cmd, '-T', 'none' if $src_scfg->{type} eq 'zfspool';
push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path;
if ($is_zero_initialized) {
push @$cmd, "zeroinit:$dst_path";
sub qemu_img_format {
my ($scfg, $volname) = @_;
- if ($scfg->{path} && $volname =~ m/\.(raw|cow|qcow|qcow2|qed|vmdk|cloop)$/) {
+ if ($scfg->{path} && $volname =~ m/\.($QEMU_FORMAT_RE)$/) {
return $1;
} else {
return "raw";
my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
print "create full clone of drive $drivename ($drive->{file})\n";
- $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, undef, ($size/1024));
+ my $name = undef;
+ if (drive_is_cloudinit($drive)) {
+ $name = "vm-$newvmid-cloudinit";
+ # cloudinit only supports raw and qcow2 atm:
+ if ($dst_format eq 'qcow2') {
+ $name .= '.qcow2';
+ } elsif ($dst_format ne 'raw') {
+ die "clone: unhandled format for cloudinit image\n";
+ }
+ }
+ $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024));
push @$newvollist, $newvolid;
PVE::Storage::activate_volumes($storecfg, [$newvolid]);
$machine = PVE::QemuServer::get_current_qemu_machine($vmid) if !$machine;
- foreach my $opt (keys %$conf) {
- next if $opt !~ m/^net(\d+)$/;
- my $net = PVE::QemuServer::parse_net($conf->{$opt});
- next if !$net;
- my $romfile = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, 'qom-get', path => $opt, property => 'romfile');
- return $machine.".pxe" if $romfile =~ m/pxe/;
- last;
+ if ($conf->{machine} && $conf->{machine} =~ m/\.pxe$/) {
+ $machine .= '.pxe';
}
return $machine;
return $res;
}
+sub nbd_stop {
+ my ($vmid) = @_;
+
+ vm_mon_cmd($vmid, 'nbd-server-stop');
+}
+
1;