From 9dfd5fc23a90cea58628e69bb54aa45eff459ea5 Mon Sep 17 00:00:00 2001 From: Apollon Oikonomopoulos <apollon@noc.grnet.gr> Date: Mon, 22 Nov 2010 17:01:07 +0200 Subject: [PATCH] Add lib/ippool.py: IP pool management primitives This patch introduces primitive structures for IP pool management. The core of the IP Pool management framework is the ippool.IPv4Network class, which encodes an IPv4 subnet configuration with its IP address pool. The pool functionality depends on python-bitarray and python-ipaddr is used for address manipulation. ippool.IPv4Network has 3 key attributes: - net: The IPv4 subnet definition (CIDR notation) - gateway: The default gateway used, if any - _pool: a bitarray.bitarray storing the reservation status of individual IPs The purpose of including ippool.IPv4Network in ganeti is two-fold: - It will allow including subnet information useful for network configuration (e.g. by being available during an OS provider invocation) - It will be used for automatic IP address allocation during instance creation for both, routed and bridged modes. Signed-off-by: Apollon Oikonomopoulos <apollon@noc.grnet.gr> --- Makefile.am | 1 + lib/ippool.py | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 lib/ippool.py diff --git a/Makefile.am b/Makefile.am index 90cc61b25..91ab8a47d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -160,6 +160,7 @@ pkgpython_PYTHON = \ lib/ssh.py \ lib/storage.py \ lib/uidpool.py \ + lib/ippool.py \ lib/workerpool.py client_PYTHON = \ diff --git a/lib/ippool.py b/lib/ippool.py new file mode 100644 index 000000000..d841e5680 --- /dev/null +++ b/lib/ippool.py @@ -0,0 +1,220 @@ +# +# + +# Copyright (C) 2010 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + + +"""Ip address pool management functions. + +""" + +import base64 +import ipaddr + +from ganeti import errors + +from bitarray import bitarray + + +class IPv4PoolError(Exception): + """ Generic IPv4 pool error + + """ + + +class IPv4PoolFull(IPv4PoolError): + """ IPv4 pool-is-full error + + """ + + +class IPv4Network(object): + def __init__(self, net, gateway=None, pool=None): + """ Initialize a new IPv4 address pool + + """ + self.net = ipaddr.IPv4Network(net) + self.gateway = None + self.size = 2**(32 - self.net.prefixlen) + if self.size <= 2: + raise IPv4PoolError("Subnet too small") + + if pool is None: + self._pool = bitarray(self.size) + self._pool.setall(False) + self._pool[0] = True + self._pool[-1] = True + else: + self._pool = bitarray() + self._pool.fromstring(pool) + + if gateway is not None: + self.gateway = ipaddr.IPv4Address(gateway) + + if not self.net.Contains(self.gateway): + raise IPv4PoolError("Gateway must lie in the subnet") + + if pool is None: + try: + self.reserve(gateway) + except IPv4PoolError: + raise IPv4PoolError("Gateway cannot be network or broadcast") + + def __repr__(self): + return "<IPv4Network: %s, gateway: %s, free: %d>" % (self.net, self.gateway, + self.free_count) + + def todict(self): + """Convert an IPv4Network to a dictionary + + """ + d = {} + d["net"] = str(self.net) + if self.gateway is not None: + d["gateway"] = str(self.gateway) + + # JSON can't handle binary data, so encode the pool using base64 + d["pool"] = base64.encodestring(self._pool.tostring()).strip() + return d + + @classmethod + def fromdict(cls, d): + """Create an IPv4Network instance from a dictionary + + @type d: dict + @param d: dictionary containing the pool data + @rtype: IPv4Network + @return: IPv4Network object for the specified pool + + """ + pool = None + gateway = None + if "pool" in d: + pool = base64.decodestring(d["pool"]) + if "gateway" in d: + gateway = d["gateway"] + + return cls(d["net"], gateway, pool) + + @property + def full(self): + """Check whether the pool is full + + """ + return self._pool.all() + + @property + def reserved_count(self): + """Get the count of reserved IPs + + """ + return self._pool.count(True) + + @property + def free_count(self): + """Get the count of free IPs + + """ + return self._pool.count(False) + + @property + def map(self): + """Return an intuitive textual representation of the pool status. + + """ + ipmap = "" + for ip in self._pool: + if ip: + ipmap += "X" + else: + ipmap += "." + return ipmap + + def _ip_index(self, address): + addr = ipaddr.IPv4Address(address) + + if not self.net.Contains(addr): + raise IPv4PoolError("%s does not contain %s" % (self.net, address)) + + idx = int(addr) - int(self.net.network) + return idx + + def _mark(self, address, value=True): + idx = self._ip_index(address) + self._pool[idx] = value + + def is_reserved(self, address): + """Check whether an address is reserved + + """ + idx = self._ip_index(address) + return self._pool[idx] + + def reserve(self, address): + """Mark an address as used. + + Fail if the address is already marked as used + """ + if self.is_reserved(address): + raise IPv4PoolError("%s already reserved" % address) + self._mark(address) + + def release(self, address): + """Mark an address as free + + """ + self._mark(address, False) + + def get_free_address(self): + """Return the first free address in the pool + + """ + if self.full: + raise IPv4PoolFull("Pool is full") + idx = self._pool.index(False) + addr = str(self.net[idx]) + self.reserve(addr) + return addr + + def __iter_free(self): + for idx in self._pool.search("0", 64): + yield str(self.net[idx]) + + def generate_free(self): + return self.__iter_free().next + + +def ParseIPv4Pool(value): + """Parse an IP address pool definition + + @type value: str + @param value: string representation of the user-id pool. + The accepted input format is the following: + <CIDR> <gateway> + Example: 10.0.1.0/24 10.0.1.1 + + @rtype: tuple + @return: A tuple (start, end, prefix, gateway) + + """ + components = value.split() + if len(components) != 2: + raise errors.OpPrereqError("Invalid IP pool definition", errors.ECODE_INVAL) + + return IPv4Network(*components) + -- GitLab