* 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

Reply via email to