X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer%2FCloudinit.pm;h=5be820c22d58b0cc0014e7dab6412f8d2db9132d;hb=c701be32431405e7b45c966d65a1825594e44288;hp=c026d79fe8540a596c8b74af300c7bc8565ce81e;hpb=e8ac21381ee32e780fc540c756cdd3e50b1a193c;p=qemu-server.git diff --git a/PVE/QemuServer/Cloudinit.pm b/PVE/QemuServer/Cloudinit.pm index c026d79..5be820c 100644 --- a/PVE/QemuServer/Cloudinit.pm +++ b/PVE/QemuServer/Cloudinit.pm @@ -12,18 +12,38 @@ use PVE::Storage; use PVE::QemuServer; sub commit_cloudinit_disk { - my ($conf, $drive, $volname, $storeid, $file_path, $label) = @_; + my ($conf, $vmid, $drive, $volname, $storeid, $files, $label) = @_; + + my $path = "/run/pve/cloudinit/$vmid/"; + mkpath $path; + foreach my $filepath (keys %$files) { + if ($filepath !~ m@^(.*)\/[^/]+$@) { + die "internal error: bad file name in cloud-init image: $filepath\n"; + } + my $dirname = $1; + mkpath "$path/$dirname"; + + my $contents = $files->{$filepath}; + file_set_contents("$path/$filepath", $contents); + } my $storecfg = PVE::Storage::config(); my $iso_path = PVE::Storage::path($storecfg, $drive->{file}); my $scfg = PVE::Storage::storage_config($storecfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + $plugin->activate_volume($storeid, $scfg, $volname); my $format = PVE::QemuServer::qemu_img_format($scfg, $volname); my $size = PVE::Storage::file_size_info($iso_path); - run_command([['genisoimage', '-R', '-V', $label, $file_path], - ['qemu-img', 'dd', '-f', 'raw', '-O', $format, - 'isize=0', "osize=$size", "of=$iso_path"]]); + eval { + run_command([['genisoimage', '-R', '-V', $label, $path], + ['qemu-img', 'dd', '-n', '-f', 'raw', '-O', $format, + 'isize=0', "osize=$size", "of=$iso_path"]]); + }; + my $err = $@; + rmtree($path); + die $err if $err; } sub get_cloudinit_format { @@ -45,8 +65,8 @@ sub get_cloudinit_format { } sub get_hostname_fqdn { - my ($conf) = @_; - my $hostname = $conf->{name}; + my ($conf, $vmid) = @_; + my $hostname = $conf->{name} // "VM$vmid"; my $fqdn; if ($hostname =~ /\./) { $fqdn = $hostname; @@ -57,15 +77,35 @@ sub get_hostname_fqdn { return ($hostname, $fqdn); } -sub cloudinit_userdata { +sub get_dns_conf { my ($conf) = @_; - my ($hostname, $fqdn) = get_hostname_fqdn($conf); + # Same logic as in pve-container, but without the testcase special case + my $host_resolv_conf = PVE::INotify::read_file('resolvconf'); + + my $searchdomains = [ + split(/\s+/, $conf->{searchdomain} // $host_resolv_conf->{search}) + ]; + + my $nameserver = $conf->{nameserver}; + if (!defined($nameserver)) { + $nameserver = [grep { $_ } $host_resolv_conf->@{qw(dns1 dns2 dns3)}]; + } else { + $nameserver = [split(/\s+/, $nameserver)]; + } + + return ($searchdomains, $nameserver); +} + +sub cloudinit_userdata { + my ($conf, $vmid) = @_; + + my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid); my $content = "#cloud-config\n"; - $content .= "manage_resolv_conf: true\n"; $content .= "hostname: $hostname\n"; + $content .= "manage_etc_hosts: true\n"; $content .= "fqdn: $fqdn\n" if defined($fqdn); my $username = $conf->{ciuser}; @@ -97,11 +137,28 @@ sub cloudinit_userdata { return $content; } +sub split_ip4 { + my ($ip) = @_; + my ($addr, $mask) = split('/', $ip); + die "not a CIDR: $ip\n" if !defined $mask; + return ($addr, $PVE::Network::ipv4_reverse_mask->[$mask]); +} + sub configdrive2_network { my ($conf) = @_; my $content = "auto lo\n"; - $content .="iface lo inet loopback\n\n"; + $content .= "iface lo inet loopback\n\n"; + + my ($searchdomains, $nameservers) = get_dns_conf($conf); + if ($nameservers && @$nameservers) { + $nameservers = join(' ', @$nameservers); + $content .= " dns_nameservers $nameservers\n"; + } + if ($searchdomains && @$searchdomains) { + $searchdomains = join(' ', @$searchdomains); + $content .= " dns_search $searchdomains\n"; + } my @ifaces = grep(/^net(\d+)$/, keys %$conf); foreach my $iface (@ifaces) { @@ -115,10 +172,10 @@ sub configdrive2_network { if ($net->{ip} eq 'dhcp') { $content .= "iface $id inet dhcp\n"; } else { - my ($addr, $mask) = split('/', $net->{ip}); + my ($addr, $mask) = split_ip4($net->{ip}); $content .= "iface $id inet static\n"; $content .= " address $addr\n"; - $content .= " netmask $PVE::Network::ipv4_reverse_mask->[$mask]\n"; + $content .= " netmask $mask\n"; $content .= " gateway $net->{gw}\n" if $net->{gw}; } } @@ -135,9 +192,6 @@ sub configdrive2_network { } } - $content .=" dns_nameservers $conf->{nameserver}\n" if $conf->{nameserver}; - $content .=" dns_search $conf->{searchdomain}\n" if $conf->{searchdomain}; - return $content; } @@ -154,7 +208,7 @@ EOF sub generate_configdrive2 { my ($conf, $vmid, $drive, $volname, $storeid) = @_; - my $user_data = cloudinit_userdata($conf); + my $user_data = cloudinit_userdata($conf, $vmid); my $network_data = configdrive2_network($conf); my $digest_data = $user_data . $network_data; @@ -162,20 +216,12 @@ sub generate_configdrive2 { my $meta_data = configdrive2_metadata($uuid_str); - mkdir "/tmp/cloudinit"; - my $path = "/tmp/cloudinit/$vmid"; - mkdir $path; - mkdir "$path/drive"; - mkdir "$path/drive/openstack"; - mkdir "$path/drive/openstack/latest"; - mkdir "$path/drive/openstack/content"; - file_set_contents("$path/drive/openstack/latest/user_data", $user_data); - file_set_contents("$path/drive/openstack/content/0000", $network_data); - file_set_contents("$path/drive/openstack/latest/meta_data.json", $meta_data); - - commit_cloudinit_disk($conf, $drive, $volname, $storeid, "$path/drive", 'config-2'); - - rmtree("$path/drive"); + my $files = { + '/openstack/latest/user_data' => $user_data, + '/openstack/content/0000' => $network_data, + '/openstack/latest/meta_data.json' => $meta_data + }; + commit_cloudinit_disk($conf, $vmid, $drive, $volname, $storeid, $files, 'config-2'); } sub nocloud_network_v2 { @@ -186,7 +232,7 @@ sub nocloud_network_v2 { my $head = "version: 2\n" . "ethernets:\n"; - my $nameservers_done; + my $dns_done; my @ifaces = grep(/^net(\d+)$/, keys %$conf); foreach my $iface (@ifaces) { @@ -202,55 +248,52 @@ sub nocloud_network_v2 { my $mac = $net->{macaddr} or die "network interface '$iface' has no mac address\n"; - my $data = "${i}$iface:\n"; + $content .= "${i}$iface:\n"; $i .= ' '; - $data .= "${i}match:\n" - . "${i} macaddress: \"$mac\"\n" - . "${i}set-name: eth$id\n"; + $content .= "${i}match:\n" + . "${i} macaddress: \"$mac\"\n" + . "${i}set-name: eth$id\n"; my @addresses; if (defined(my $ip = $ipconfig->{ip})) { if ($ip eq 'dhcp') { - $data .= "${i}dhcp4: true\n"; + $content .= "${i}dhcp4: true\n"; } else { push @addresses, $ip; } } if (defined(my $ip = $ipconfig->{ip6})) { if ($ip eq 'dhcp') { - $data .= "${i}dhcp6: true\n"; + $content .= "${i}dhcp6: true\n"; } else { push @addresses, $ip; } } if (@addresses) { - $data .= "${i}addresses:\n"; - $data .= "${i}- $_\n" foreach @addresses; + $content .= "${i}addresses:\n"; + $content .= "${i}- $_\n" foreach @addresses; } if (defined(my $gw = $ipconfig->{gw})) { - $data .= "${i}gateway4: $gw\n"; + $content .= "${i}gateway4: $gw\n"; } if (defined(my $gw = $ipconfig->{gw6})) { - $data .= "${i}gateway6: $gw\n"; + $content .= "${i}gateway6: $gw\n"; } - if (!$nameservers_done) { - $nameservers_done = 1; - - my $nameserver = $conf->{nameserver} // ''; - my $searchdomain = $conf->{searchdomain} // ''; - my @nameservers = PVE::Tools::split_list($nameserver); - my @searchdomains = PVE::Tools::split_list($searchdomain); - if (@nameservers || @searchdomains) { - $data .= "${i}nameservers:\n"; - $data .= "${i} addresses: [".join(',', @nameservers)."]\n" - if @nameservers; - $data .= "${i} search: [".join(',', @searchdomains)."]\n" - if @searchdomains; + next if $dns_done; + $dns_done = 1; + + my ($searchdomains, $nameservers) = get_dns_conf($conf); + if ($searchdomains || $nameservers) { + $content .= "${i}nameservers:\n"; + if (defined($nameservers) && @$nameservers) { + $content .= "${i} addresses:\n"; + $content .= "${i} - $_\n" foreach @$nameservers; + } + if (defined($searchdomains) && @$searchdomains) { + $content .= "${i} search:\n"; + $content .= "${i} - $_\n" foreach @$searchdomains; } } - - - $content .= $data; } return $head.$content; @@ -273,54 +316,54 @@ sub nocloud_network { my $net = PVE::QemuServer::parse_net($conf->{$iface}); my $ipconfig = PVE::QemuServer::parse_ipconfig($conf->{"ipconfig$id"}); - my $mac = $net->{macaddr} + my $mac = lc($net->{macaddr}) or die "network interface '$iface' has no mac address\n"; - my $data = "${i}- type: physical\n" - . "${i} name: eth$id\n" - . "${i} mac_address: $mac\n" - . "${i} subnets:\n"; + $content .= "${i}- type: physical\n" + . "${i} name: eth$id\n" + . "${i} mac_address: $mac\n" + . "${i} subnets:\n"; $i .= ' '; if (defined(my $ip = $ipconfig->{ip})) { if ($ip eq 'dhcp') { - $data .= "${i}- type: dhcp4\n"; + $content .= "${i}- type: dhcp4\n"; } else { - $data .= "${i}- type: static\n" - . "${i} address: $ip\n"; + my ($addr, $mask) = split_ip4($ip); + $content .= "${i}- type: static\n" + . "${i} address: $addr\n" + . "${i} netmask: $mask\n"; if (defined(my $gw = $ipconfig->{gw})) { - $data .= "${i} gateway: $gw\n"; + $content .= "${i} gateway: $gw\n"; } } } if (defined(my $ip = $ipconfig->{ip6})) { if ($ip eq 'dhcp') { - $data .= "${i}- type: dhcp6\n"; + $content .= "${i}- type: dhcp6\n"; + } elsif ($ip eq 'auto') { + # SLAAC is not supported by cloud-init, this fallback should work with an up-to-date netplan at least + $content .= "${i}- type: dhcp6\n"; } else { - $data .= "${i}- type: static6\n" + $content .= "${i}- type: static\n" . "${i} address: $ip\n"; if (defined(my $gw = $ipconfig->{gw6})) { - $data .= "${i} gateway: $gw\n"; + $content .= "${i} gateway: $gw\n"; } } } - - $content .= $data; } - my $nameserver = $conf->{nameserver} // ''; - my $searchdomain = $conf->{searchdomain} // ''; - my @nameservers = PVE::Tools::split_list($nameserver); - my @searchdomains = PVE::Tools::split_list($searchdomain); - if (@nameservers || @searchdomains) { - my $i = ' '; + my $i = ' '; + my ($searchdomains, $nameservers) = get_dns_conf($conf); + if ($searchdomains || $nameservers) { $content .= "${i}- type: nameserver\n"; - if (@nameservers) { + if (defined($nameservers) && @$nameservers) { $content .= "${i} address:\n"; - $content .= "${i} - $_\n" foreach @nameservers; + $content .= "${i} - $_\n" foreach @$nameservers; } - if (@searchdomains) { + if (defined($searchdomains) && @$searchdomains) { $content .= "${i} search:\n"; - $content .= "${i} - $_\n" foreach @searchdomains; + $content .= "${i} - $_\n" foreach @$searchdomains; } } @@ -335,7 +378,7 @@ sub nocloud_metadata { sub generate_nocloud { my ($conf, $vmid, $drive, $volname, $storeid) = @_; - my $user_data = cloudinit_userdata($conf); + my $user_data = cloudinit_userdata($conf, $vmid); my $network_data = nocloud_network($conf); my $digest_data = $user_data . $network_data; @@ -343,17 +386,12 @@ sub generate_nocloud { my $meta_data = nocloud_metadata($uuid_str); - mkdir "/tmp/cloudinit"; - my $path = "/tmp/cloudinit/$vmid"; - mkdir $path; - rmtree("$path/drive"); - mkdir "$path/drive"; - file_set_contents("$path/drive/user-data", $user_data); - file_set_contents("$path/drive/network-config", $network_data); - file_set_contents("$path/drive/meta-data", $meta_data); - - commit_cloudinit_disk($conf, $drive, $volname, $storeid, "$path/drive", 'cidata'); - + my $files = { + '/user-data' => $user_data, + '/network-config' => $network_data, + '/meta-data' => $meta_data + }; + commit_cloudinit_disk($conf, $vmid, $drive, $volname, $storeid, $files, 'cidata'); } my $cloudinit_methods = {