commit 22dc14c47fb4491c688c4c9c9a03971f3d6c9a92
Author: Dimitris Aragiorgis <dimara@grnet.gr>
Date:   Fri Mar 1 21:11:32 2013 +0200

    Total refactor of network module
    
    Add GenericNetwork class to implement basic validation methods
    for an IPv4/IPv6 network basically conserning gateways and subnets.
    
    Make AddressPool class extend GenericNetwork and implement methods
    that reserve/release IPs in a IPv4 address pool (using bitarray).
    After any of these operations updates the corresponding reservations
    of L{objects.Network}.
    
    Add Network class which is a wrapper of those two. If L{objects.Network}
    has an IPv4 network then an AddressPool is returned otherwise a
    GenericNetwork. Furthermore implement a method that checks if a
    given address, network pair is valid.
    
    External code should only do:
    
    nobj = objects.Network(...)
    network = network.Network(nobj)
    
    Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>

diff --git a/lib/network.py b/lib/network.py
index d78b717..17089f4 100644
--- a/lib/network.py
+++ b/lib/network.py
@@ -28,6 +28,8 @@ import ipaddr
 from bitarray import bitarray
 
 from ganeti import errors
+from ganeti import utils
+from ganeti import constants
 
 
 def _ComputeIpv4NumHosts(network_size):
@@ -45,7 +47,117 @@ IPV4_NETWORK_MIN_NUM_HOSTS = _ComputeIpv4NumHosts(IPV4_NETWORK_MIN_SIZE)
 IPV4_NETWORK_MAX_NUM_HOSTS = _ComputeIpv4NumHosts(IPV4_NETWORK_MAX_SIZE)
 
 
-class AddressPool(object):
+class Network(object):
+  """ Wrapper Class for networks.
+
+  Used to get a network out of a L{objects.Network}. In case nobj
+  has an IPv4 subnet it returns an AddressPool object. Otherwise
+  a GenericNetwork object is created. To get a network use:
+  network.Network(nobj)
+
+  """
+  def __new__(cls, nobj):
+    if nobj.network:
+      return AddressPool(nobj)
+    else:
+      return GenericNetwork(nobj)
+
+  @classmethod
+  def Check(cls, address, network):
+    try:
+      if network:
+        network = ipaddr.IPNetwork(network)
+      if address:
+        address = ipaddr.IPAddress(address)
+    except ValueError, e:
+      raise errors.OpPrereqError(e, errors.ECODE_INVAL)
+
+    if address and not network:
+      raise errors.OpPrereqError("Address '%s' but no network." % address,
+                                 errors.ECODE_INVAL)
+    if address and address not in network:
+      raise errors.OpPrereqError("Address '%s' not in network '%s'." %
+                                 (address, network),
+                                 errors.ECODE_INVAL)
+
+
+class GenericNetwork(object):
+  """ Base class for networks.
+
+  This includes all info and methods deriving from subnets and gateways
+  both IPv4 and IPv6. Implements basic checks and abstracts the methods
+  that are invoked by external methods.
+
+  """
+  def __init__(self, nobj):
+    """Initializes a Generic Network from an L{objects.Network} object.
+
+    @type nobj: L{objects.Network}
+    @param nobj: the network object
+
+    """
+    self.network = None
+    self.gateway = None
+    self.network6 = None
+    self.gateway6 = None
+
+    self.nobj = nobj
+
+    if self.nobj.gateway and not self.nobj.network:
+      raise errors.OpPrereqError("Gateway without network. Cannot proceed")
+
+    if self.nobj.network:
+      self.network = ipaddr.IPNetwork(self.nobj.network)
+      if self.nobj.gateway:
+        self.gateway = ipaddr.IPAddress(self.nobj.gateway)
+        if self.gateway not in self.network:
+          raise errors.OpPrereqError("Gateway not in network.",
+                                     errors.ECODE_INVAL)
+
+    if self.nobj.gateway6 and not self.nobj.network6:
+      raise errors.OpPrereqError("IPv6 Gateway without IPv6 network."
+                                 " Cannot proceed.",
+                                 errors.ECODE_INVAL)
+    if self.nobj.network6:
+      self.network6 = ipaddr.IPv6Network(self.nobj.network6)
+      if self.nobj.gateway6:
+        self.gateway6 = ipaddr.IPv6Address(self.nobj.gateway6)
+        if self.gateway6 not in self.network6:
+          raise errors.OpPrereqError("IPv6 Gateway not in IPv6 network.",
+                                     errors.ECODE_INVAL)
+
+  def _Validate(self):
+    if self.gateway:
+      assert self.network
+      assert self.gateway in self.network
+    if self.gateway6:
+      assert self.network6
+      assert self.gateway6 in self.network6
+
+  def Contains(self, address):
+    addr = ipaddr.IPAddress(address)
+    if addr.version == constants.IP4_VERSION and self.network:
+      return addr in self.network
+    elif addr.version == constants.IP6_VERSION and self.network6:
+      return addr in self.network6
+
+  def IsReserved(self, address):
+    raise NotImplementedError
+
+  def Reserve(self, address, external):
+    raise NotImplementedError
+
+  def Release(self, address, external):
+    raise NotImplementedError
+
+  def GenerateFree(self):
+    raise NotImplementedError
+
+  def GetStats(self):
+    return {}
+
+
+class AddressPool(GenericNetwork):
   """Address pool class, wrapping an C{objects.Network} object.
 
   This class provides methods to manipulate address pools, backed by
@@ -55,76 +167,43 @@ class AddressPool(object):
   FREE = bitarray("0")
   RESERVED = bitarray("1")
 
-  def __init__(self, network):
+  def __init__(self, nobj):
     """Initialize a new IPv4 address pool from an L{objects.Network} object.
 
     @type network: L{objects.Network}
     @param network: the network object from which the pool will be generated
 
     """
-    self.network = None
-    self.gateway = None
-    self.network6 = None
-    self.gateway6 = None
-
-    self.net = network
-
-    self.network = ipaddr.IPNetwork(self.net.network)
-    if self.network.numhosts > IPV4_NETWORK_MAX_NUM_HOSTS:
-      raise errors.AddressPoolError("A big network with %s host(s) is currently"
-                                    " not supported. please specify at most a"
-                                    " /%s network" %
-                                    (str(self.network.numhosts),
-                                     IPV4_NETWORK_MAX_SIZE))
-
-    if self.network.numhosts < IPV4_NETWORK_MIN_NUM_HOSTS:
-      raise errors.AddressPoolError("A network with only %s host(s) is too"
-                                    " small, please specify at least a /%s"
-                                    " network" %
-                                    (str(self.network.numhosts),
-                                     IPV4_NETWORK_MIN_SIZE))
-    if self.net.gateway:
-      self.gateway = ipaddr.IPAddress(self.net.gateway)
-
-    if self.net.network6:
-      self.network6 = ipaddr.IPv6Network(self.net.network6)
-    if self.net.gateway6:
-      self.gateway6 = ipaddr.IPv6Address(self.net.gateway6)
-
-    if self.net.reservations:
-      self.reservations = bitarray(self.net.reservations)
+    super(AddressPool, self).__init__(nobj)
+    if self.nobj.reservations and self.nobj.ext_reservations:
+      self.reservations = bitarray(self.nobj.reservations)
+      self.ext_reservations = bitarray(self.nobj.ext_reservations)
     else:
-      self.reservations = bitarray(self.network.numhosts)
-      # pylint: disable=E1103
-      self.reservations.setall(False)
+      self._InitializeReservations()
 
-    if self.net.ext_reservations:
-      self.ext_reservations = bitarray(self.net.ext_reservations)
-    else:
-      self.ext_reservations = bitarray(self.network.numhosts)
-      # pylint: disable=E1103
-      self.ext_reservations.setall(False)
+    self._Validate()
 
-    assert len(self.reservations) == self.network.numhosts
-    assert len(self.ext_reservations) == self.network.numhosts
+  def _InitializeReservations(self):
+    self.reservations = bitarray(self.network.numhosts)
+    self.reservations.setall(False) # pylint: disable=E1103
 
-  def Contains(self, address):
-    if address is None:
-      return False
-    addr = ipaddr.IPAddress(address)
+    self.ext_reservations = bitarray(self.network.numhosts)
+    self.ext_reservations.setall(False) # pylint: disable=E1103
 
-    return addr in self.network
+    for ip in [self.network[0], self.network[-1]]:
+      self.Reserve(ip, external=True)
 
-  def _GetAddrIndex(self, address):
-    addr = ipaddr.IPAddress(address)
+    if self.nobj.gateway:
+      self.Reserve(self.nobj.gateway, external=True)
 
-    if not addr in self.network:
-      raise errors.AddressPoolError("%s does not contain %s" %
-                                    (self.network, addr))
+    self._Update()
 
+  def _GetAddrIndex(self, address):
+    addr = ipaddr.IPAddress(address)
+    assert addr in self.network
     return int(addr) - int(self.network.network)
 
-  def Update(self):
+  def _Update(self):
     """Write address pools back to the network object.
 
     """
@@ -138,62 +217,57 @@ class AddressPool(object):
       self.ext_reservations[idx] = value
     else:
       self.reservations[idx] = value
-    self.Update()
+    self._Update()
 
   def _GetSize(self):
     return 2 ** (32 - self.network.prefixlen)
 
   @property
-  def all_reservations(self):
+  def _all_reservations(self):
     """Return a combined map of internal and external reservations.
 
     """
     return (self.reservations | self.ext_reservations)
 
-  def Validate(self):
-    assert len(self.reservations) == self._GetSize()
-    assert len(self.ext_reservations) == self._GetSize()
+  def _Validate(self):
+    super(AddressPool, self)._Validate()
+    assert len(self.reservations) == self.network.numhosts
+    assert len(self.ext_reservations) == self.network.numhosts
     all_res = self.reservations & self.ext_reservations
     assert not all_res.any()
 
-    if self.gateway is not None:
-      assert self.gateway in self.network
-
-    if self.network6 and self.gateway6:
-      assert self.gateway6 in self.network6
-
-    return True
-
-  def IsFull(self):
-    """Check whether the network is full.
-
-    """
-    return self.all_reservations.all()
-
-  def GetReservedCount(self):
+  def _GetReservedCount(self):
     """Get the count of reserved addresses.
 
     """
-    return self.all_reservations.count(True)
+    return self._all_reservations.count(True)
 
-  def GetFreeCount(self):
+  def _GetFreeCount(self):
     """Get the count of unused addresses.
 
     """
-    return self.all_reservations.count(False)
+    return self._all_reservations.count(False)
 
-  def GetMap(self):
+  def _GetMap(self):
     """Return a textual representation of the network's occupation status.
 
     """
-    return self.all_reservations.to01().replace("1", "X").replace("0", ".")
+    return self._all_reservations.to01().replace("1", "X").replace("0", ".")
+
+  def _GetExternalReservations(self):
+    """Returns a list of all externally reserved addresses.
+
+    """
+    # pylint: disable=E1103
+    idxs = self.ext_reservations.search(self.RESERVED)
+    return [str(self.network[idx]) for idx in idxs]
 
   def IsReserved(self, address):
     """Checks if the given IP is reserved.
 
     """
     idx = self._GetAddrIndex(address)
-    return self.all_reservations[idx]
+    return self._all_reservations[idx]
 
   def Reserve(self, address, external=False):
     """Mark an address as used.
@@ -227,32 +301,19 @@ class AddressPool(object):
     @raise errors.AddressPoolError: Pool is full
 
     """
-    idx = self.all_reservations.search(self.FREE, 1)
-    if idx:
-      return str(self.network[idx[0]])
-    else:
-      raise errors.AddressPoolError("%s is full" % self.network)
-
-  def GetExternalReservations(self):
-    """Returns a list of all externally reserved addresses.
-
-    """
-    # pylint: disable=E1103
-    idxs = self.ext_reservations.search(self.RESERVED)
-    return [str(self.network[idx]) for idx in idxs]
-
-  @classmethod
-  def InitializeNetwork(cls, net):
-    """Initialize an L{objects.Network} object.
+    idx = self._all_reservations.search(self.FREE, 1)
+    if not idx:
+      raise errors.NetworkError("%s is full" % self.network)
+    return str(self.network[idx[0]])
 
-    Reserve the network, broadcast and gateway IP addresses.
+  def GetStats(self):
+    """Returns statistics for a network address pool.
 
     """
-    obj = cls(net)
-    obj.Update()
-    for ip in [obj.network[0], obj.network[-1]]:
-      obj.Reserve(ip, external=True)
-    if obj.net.gateway is not None:
-      obj.Reserve(obj.net.gateway, external=True)
-    obj.Validate()
-    return obj
+    return {
+      "free_count": self._GetFreeCount(),
+      "reserved_count": self._GetReservedCount(),
+      "map": self._GetMap(),
+      "external_reservations":
+        utils.CommaJoin(self._GetExternalReservations()),
+      }
