Modify LUInstanceCreate to support networks
authorDimitris Aragiorgis <dimara@grnet.gr>
Mon, 4 Jun 2012 19:31:30 +0000 (22:31 +0300)
committerIustin Pop <iustin@google.com>
Tue, 20 Nov 2012 17:51:02 +0000 (18:51 +0100)
Implement backend support, to export the IP pool management
functionality to the clients. When the new NIC parameter 'network' is
given, the ippool management system is triggered.  If a NIC belongs to
a network, it inherits the netparams (mode, link) as its nicparams. If
a network is requested, then no 'mode' or 'link' should be
specified. Backwards compatibility is preserved, if 'network'
parameter is omitted.

Examples for IP pool management system:

--net 0:network=net1
  for an IPless NIC in a network

--net 0:ip=pool,network=net1
  for automatically generated IP inside the network

--net 0:ip=1.2.3.4,network=net1
  for specific IP inside the network

--net 0:network=none
  for IPless NIC with default nicparams

Examples for traditional assignment:

--net 0:ip=1.2.3.4,link=br100,mode=bridged
--net 0:ip=none,link=rt2,mode=routed
--net 0

Check for conflicting IPs to ensure IP uniqueness inside nodegroups.

IAllocator should be extended to become network aware. If an instance
has a NIC inside a network, then IAllocator must pick a node that its
nodegroup is already connected with that network.

Signed-off-by: Apollon Oikonomopoulos <apollon@noc.grnet.gr>
Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>
Reviewed-by: Iustin Pop <iustin@google.com>

lib/cmdlib.py
lib/config.py

index 3e7e6bd..15a99fb 100644 (file)
@@ -1340,7 +1340,7 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
   @type vcpus: string
   @param vcpus: the count of VCPUs the instance has
   @type nics: list
-  @param nics: list of tuples (ip, mac, mode, link) representing
+  @param nics: list of tuples (ip, mac, mode, link, network) representing
       the NICs the instance has
   @type disk_template: string
   @param disk_template: the disk template of the instance
@@ -1375,13 +1375,14 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
   }
   if nics:
     nic_count = len(nics)
-    for idx, (ip, mac, mode, link) in enumerate(nics):
+    for idx, (ip, mac, mode, link, network) in enumerate(nics):
       if ip is None:
         ip = ""
       env["INSTANCE_NIC%d_IP" % idx] = ip
       env["INSTANCE_NIC%d_MAC" % idx] = mac
       env["INSTANCE_NIC%d_MODE" % idx] = mode
       env["INSTANCE_NIC%d_LINK" % idx] = link
+      env["INSTANCE_NIC%d_NETWORK" % idx] = network
       if mode == constants.NIC_MODE_BRIDGED:
         env["INSTANCE_NIC%d_BRIDGE" % idx] = link
   else:
@@ -1431,7 +1432,8 @@ def _NICListToTuple(lu, nics):
     filled_params = cluster.SimpleFillNIC(nic.nicparams)
     mode = filled_params[constants.NIC_MODE]
     link = filled_params[constants.NIC_LINK]
-    hooks_nics.append((ip, mac, mode, link))
+    network = nic.network
+    hooks_nics.append((ip, mac, mode, link, network))
   return hooks_nics
 
 
@@ -9411,14 +9413,19 @@ def _ComputeNics(op, cluster, default_ip, cfg, proc):
     if nic_mode is None or nic_mode == constants.VALUE_AUTO:
       nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
 
-    # in routed mode, for the first nic, the default ip is 'auto'
-    if nic_mode == constants.NIC_MODE_ROUTED and idx == 0:
-      default_ip_mode = constants.VALUE_AUTO
+    net = nic.get(constants.INIC_NETWORK, None)
+    link = nic.get(constants.NIC_LINK, None)
+    ip = nic.get(constants.INIC_IP, None)
+
+    if net is None or net.lower() == constants.VALUE_NONE:
+      net = None
     else:
-      default_ip_mode = constants.VALUE_NONE
+      if nic_mode_req is not None or link is not None:
+        raise errors.OpPrereqError("If network is given, no mode or link"
+                                   " is allowed to be passed",
+                                   errors.ECODE_INVAL)
 
     # ip validity checks
-    ip = nic.get(constants.INIC_IP, default_ip_mode)
     if ip is None or ip.lower() == constants.VALUE_NONE:
       nic_ip = None
     elif ip.lower() == constants.VALUE_AUTO:
@@ -9428,9 +9435,18 @@ def _ComputeNics(op, cluster, default_ip, cfg, proc):
                                    errors.ECODE_INVAL)
       nic_ip = default_ip
     else:
-      if not netutils.IPAddress.IsValid(ip):
+      # We defer pool operations until later, so that the iallocator has
+      # filled in the instance's node(s) dimara
+      if ip.lower() == constants.NIC_IP_POOL:
+        if net is None:
+          raise errors.OpPrereqError("if ip=pool, parameter network"
+                                     " must be passed too",
+                                     errors.ECODE_INVAL)
+
+      elif not netutils.IPAddress.IsValid(ip):
         raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
                                    errors.ECODE_INVAL)
+
       nic_ip = ip
 
     # TODO: check the ip address for uniqueness
@@ -9452,9 +9468,6 @@ def _ComputeNics(op, cluster, default_ip, cfg, proc):
                                    errors.ECODE_NOTUNIQUE)
 
     #  Build nic parameters
-    link = nic.get(constants.INIC_LINK, None)
-    if link == constants.VALUE_AUTO:
-      link = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_LINK]
     nicparams = {}
     if nic_mode_req:
       nicparams[constants.NIC_MODE] = nic_mode
@@ -9749,6 +9762,8 @@ class LUInstanceCreate(LogicalUnit):
     """Run the allocator based on input opcode.
 
     """
+    #TODO Export network to iallocator so that it chooses a pnode
+    #     in a nodegroup that has the desired network connected to
     req = _CreateInstanceAllocRequest(self.op, self.disks,
                                       self.nics, self.be_full)
     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
@@ -10138,6 +10153,45 @@ class LUInstanceCreate(LogicalUnit):
 
     self.secondaries = []
 
+    # Fill in any IPs from IP pools. This must happen here, because we need to
+    # know the nic's primary node, as specified by the iallocator
+    for idx, nic in enumerate(self.nics):
+      net = nic.network
+      if net is not None:
+        netparams = self.cfg.GetGroupNetParams(net, self.pnode.name)
+        if netparams is None:
+          raise errors.OpPrereqError("No netparams found for network"
+                                     " %s. Propably not connected to"
+                                     " node's %s nodegroup" %
+                                     (net, self.pnode.name),
+                                     errors.ECODE_INVAL)
+        self.LogInfo("NIC/%d inherits netparams %s" %
+                     (idx, netparams.values()))
+        nic.nicparams = dict(netparams)
+        if nic.ip is not None:
+          filled_params = cluster.SimpleFillNIC(nic.nicparams)
+          if nic.ip.lower() == constants.NIC_IP_POOL:
+            try:
+              nic.ip = self.cfg.GenerateIp(net, self.proc.GetECId())
+            except errors.ReservationError:
+              raise errors.OpPrereqError("Unable to get a free IP for NIC %d"
+                                         " from the address pool" % idx,
+                                         errors.ECODE_STATE)
+            self.LogInfo("Chose IP %s from network %s", nic.ip, net)
+          else:
+            try:
+              self.cfg.ReserveIp(net, nic.ip, self.proc.GetECId())
+            except errors.ReservationError:
+              raise errors.OpPrereqError("IP address %s already in use"
+                                         " or does not belong to network %s" %
+                                         (nic.ip, net),
+                                         errors.ECODE_NOTUNIQUE)
+      else:
+        # net is None, ip None or given
+        if self.op.conflicts_check:
+          _CheckForConflictingIp(self, nic.ip, self.pnode.name)
+
+
     # mirror node verification
     if self.op.disk_template in constants.DTS_INT_MIRROR:
       if self.op.snode == pnode.name:
@@ -15943,3 +15997,20 @@ def _GetQueryImplementation(name):
   except KeyError:
     raise errors.OpPrereqError("Unknown query resource '%s'" % name,
                                errors.ECODE_INVAL)
+
+def _CheckForConflictingIp(lu, ip, node):
+  """In case of conflicting ip raise error.
+
+  @type ip: string
+  @param ip: ip address
+  @type node: string
+  @param node: node name
+
+  """
+  (conf_net, conf_netparams) = lu.cfg.CheckIPInNodeGroup(ip, node)
+  if conf_net is not None:
+    raise errors.OpPrereqError("Conflicting IP found:"
+                               " %s <> %s." % (ip, conf_net),
+                               errors.ECODE_INVAL)
+
+  return (None, None)
index b97414c..cca0bf6 100644 (file)
@@ -1359,6 +1359,7 @@ class ConfigWriter:
     self._config_data.instances[instance.name] = instance
     self._config_data.cluster.serial_no += 1
     self._UnlockedReleaseDRBDMinors(instance.name)
+    self._UnlockedCommitTemporaryIps(ec_id)
     self._WriteConfig()
 
   def _EnsureUUID(self, item, ec_id):