* Add ipconfigX for all netX configuration options and using ip=CIDR, gw=IP, ip6=CIDR, gw6=IP as option names like in LXC. * Adding explicit ip=dhcp and ip6=dhcp options. * Removing the config-update code and instead generating the ide3 commandline in config_to_command. - Adding a conflict check to write_vm_config similar to the one for 'cdrom'. * Replacing UUID generation with a SHA1 hash of the concatenated userdata and network configuration. For this generate_cloudinit_userdata/network now returns the content variable. * Finishing ipv6 support in generate_cloudinit_network. Note that ipv4 now only defaults to dhcp if no ipv6 address was specified. (Explicitly requested dhcp is always used.) --- PVE/QemuServer.pm | 157 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 127 insertions(+), 30 deletions(-)
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 7956c50..51f1277 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -18,7 +18,6 @@ use Cwd 'abs_path'; use IPC::Open3; use JSON; use Fcntl; -use UUID; use PVE::SafeSyslog; use Storable qw(dclone); use PVE::Exception qw(raise raise_param_exc); @@ -490,8 +489,25 @@ EODESCR }; PVE::JSONSchema::register_standard_option("pve-qm-net", $netdesc); +my $ipconfigdesc = { + optional => 1, + type => 'string', format => 'pve-qm-ipconfig', + typetext => "[ip=IPv4_CIDR[,gw=IPv4_GATEWAY]][,ip6=IPv6_CIDR[,gw6=IPv6_GATEWAY]]", + description => <<'EODESCR', +Specify IP addresses and gateways for the corresponding interface. + +IP addresses use CIDR notation, gateways are optional but need an IP of the same type specified. + +The special string 'dhcp' can be used for IP addresses to use DHCP, in which case no explicit gateway should be provided. + +If cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using dhcp on IPv4. +EODESCR +}; +PVE::JSONSchema::register_standard_option("pve-qm-ipconfig", $netdesc); + for (my $i = 0; $i < $MAX_NETS; $i++) { $confdesc->{"net$i"} = $netdesc; + $confdesc->{"ipconfig$i"} = $ipconfigdesc; } my $drivename_hash; @@ -1382,18 +1398,63 @@ sub parse_net { $res->{firewall} = $1; } elsif ($kvp =~ m/^link_down=([01])$/) { $res->{link_down} = $1; - } elsif ($kvp =~ m/^cidr=($IPV6RE|$IPV4RE)\/(\d+)$/) { + } else { + return undef; + } + + } + + return undef if !$res->{model}; + + return $res; +} + +# ipconfigX ip=cidr,gw=ip,ip6=cidr,gw6=ip +sub parse_ipconfig { + my ($data) = @_; + + my $res = {}; + + foreach my $kvp (split(/,/, $data)) { + if ($kvp =~ m/^ip=dhcp$/) { + $res->{address} = 'dhcp'; + } elsif ($kvp =~ m/^ip=($IPV4RE)\/(\d+)$/) { $res->{address} = $1; $res->{netmask} = $2; - } elsif ($kvp =~ m/^gateway=($IPV6RE|$IPV4RE)$/) { + } elsif ($kvp =~ m/^gw=($IPV4RE)$/) { $res->{gateway} = $1; + } elsif ($kvp =~ m/^ip6=dhcp6?$/) { + $res->{address6} = 'dhcp'; + } elsif ($kvp =~ m/^ip6=($IPV6RE)\/(\d+)$/) { + $res->{address6} = $1; + $res->{netmask6} = $2; + } elsif ($kvp =~ m/^gw6=($IPV6RE)$/) { + $res->{gateway6} = $1; } else { return undef; } + } + if ($res->{gateway} && !$res->{address}) { + warn 'gateway specified without specifying an IP address'; + return undef; + } + if ($res->{gateway6} && !$res->{address6}) { + warn 'IPv6 gateway specified without specifying an IPv6 address'; + return undef; + } + if ($res->{gateway} && !$res->{address} eq 'dhcp') { + warn 'gateway specified together with DHCP'; + return undef; + } + if ($res->{gateway6} && !$res->{address6} eq 'dhcp') { + warn 'IPv6 gateway specified together with DHCP6'; + return undef; } - return undef if !$res->{model}; + if (!$res->{address} && !$res->{address6}) { + return { address => 'dhcp' }; + } return $res; } @@ -1614,6 +1675,17 @@ sub verify_net { die "unable to parse network options\n"; } +PVE::JSONSchema::register_format('pve-qm-ipconfig', \&verify_ipconfig); +sub verify_ipconfig { + my ($value, $noerr) = @_; + + return $value if parse_ipconfig($value); + + return undef if $noerr; + + die "unable to parse ipconfig options\n"; +} + PVE::JSONSchema::register_format('pve-qm-drive', \&verify_drive); sub verify_drive { my ($value, $noerr) = @_; @@ -1995,6 +2067,11 @@ sub write_vm_config { delete $conf->{cdrom}; } + if ($conf->{cloudinit}) { + die "option cloudinit conflicts with ide3\n" if $conf->{ide3}; + delete $conf->{cloudinit}; + } + # we do not use 'smp' any longer if ($conf->{sockets}) { delete $conf->{smp}; @@ -3115,6 +3192,8 @@ sub config_to_command { push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges); }); + generate_cloudinit_command($conf, $vmid, $storecfg, $bridges, $devices); + for (my $i = 0; $i < $MAX_NETS; $i++) { next if !$conf->{"net$i"}; my $d = parse_net($conf->{"net$i"}); @@ -6297,9 +6376,9 @@ sub generate_cloudinitconfig { mkdir "$path/drive/openstack"; mkdir "$path/drive/openstack/latest"; mkdir "$path/drive/openstack/content"; - generate_cloudinit_userdata($conf, $path); - generate_cloudinit_metadata($conf, $path); - generate_cloudinit_network($conf, $path); + my $digest_data = generate_cloudinit_userdata($conf, $path) + . generate_cloudinit_network($conf, $path); + generate_cloudinit_metadata($conf, $path, $digest_data); my $cmd = []; push @$cmd, 'genisoimage'; @@ -6310,10 +6389,18 @@ sub generate_cloudinitconfig { run_command($cmd); rmtree("$path/drive"); - my $drive = PVE::QemuServer::parse_drive('ide3', 'cloudinit,media=cdrom'); - $conf->{'ide3'} = PVE::QemuServer::print_drive($vmid, $drive); - update_config_nolock($vmid, $conf, 1); +} + +sub generate_cloudinit_command { + my ($conf, $vmid, $storecfg, $bridges, $devices) = @_; + return if !$conf->{cloudinit}; + + my $path = "/tmp/cloudinit/$vmid/configdrive.iso"; + my $drive = parse_drive('ide3', 'cloudinit,media=cdrom'); + my $drive_cmd = print_drive_full($storecfg, $vmid, $drive); + push @$devices, '-drive', $drive_cmd; + push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges); } sub generate_cloudinit_userdata { @@ -6336,15 +6423,13 @@ sub generate_cloudinit_userdata { my $fn = "$path/drive/openstack/latest/user_data"; file_write($fn, $content); - + return $content; } sub generate_cloudinit_metadata { - my ($conf, $path) = @_; + my ($conf, $path, $digest_data) = @_; - my ($uuid, $uuid_str); - UUID::generate($uuid); - UUID::unparse($uuid, $uuid_str); + my $uuid_str = Digest::SHA::sha1_hex($digest_data); my $content = "{\n"; $content .= " \"uuid\": \"$uuid_str\",\n"; @@ -6353,9 +6438,7 @@ sub generate_cloudinit_metadata { my $fn = "$path/drive/openstack/latest/meta_data.json"; - return file_write($fn, $content); - - + file_write($fn, $content); } my $ipv4_reverse_mask = [ @@ -6400,19 +6483,33 @@ sub generate_cloudinit_network { my $content = "auto lo\n"; $content .="iface lo inet loopback\n\n"; - foreach my $opt (keys %$conf) { - next if $opt !~ m/^net(\d+)$/; - my $net = parse_net($conf->{$opt}); - $opt =~ s/net/eth/; + my @ifaces = grep(/^net(\d+)$/, keys %$conf); + foreach my $iface (@ifaces) { + (my $id = $iface) =~ s/^net//; + next if !$conf->{"ipconfig$id"}; + my $net = parse_ipconfig($conf->{"ipconfig$id"}); + $id = "eth$id"; - $content .="auto $opt\n"; + $content .="auto $id\n"; if ($net->{address}) { - $content .="iface $opt inet static\n"; - $content .=" address $net->{address}\n"; - $content .=" netmask $ipv4_reverse_mask->[$net->{netmask}]\n"; - $content .=" gateway $net->{gateway}\n" if $net->{gateway}; - } else { - $content .="iface $opt inet dhcp\n"; + if ($net->{address} eq 'dhcp') { + $content .="iface $id inet dhcp\n"; + } else { + $content .="iface $id inet static\n"; + $content .=" address $net->{address}\n"; + $content .=" netmask $ipv4_reverse_mask->[$net->{netmask}]\n"; + $content .=" gateway $net->{gateway}\n" if $net->{gateway}; + } + } + if ($net->{address6}) { + if ($net->{address6} eq 'dhcp') { + $content .="iface $id inet6 dhcp\n"; + } else { + $content .="iface $id inet6 static\n"; + $content .=" address $net->{address6}\n"; + $content .=" netmask $net->{netmask6}\n"; + $content .=" gateway $net->{gateway6}\n" if $net->{gateway6}; + } } } @@ -6421,7 +6518,7 @@ sub generate_cloudinit_network { my $fn = "$path/drive/openstack/content/0000"; file_write($fn, $content); - + return $content; } -- 2.1.4 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel