| # |
| # |
| |
| # Copyright (C) 2011, 2012 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 ipaddr |
| |
| from bitarray import bitarray |
| |
| from ganeti import errors |
| |
| |
| def _ComputeIpv4NumHosts(network_size): |
| """Derives the number of hosts in an IPv4 network from the size. |
| |
| """ |
| return 2 ** (32 - network_size) |
| |
| |
| IPV4_NETWORK_MIN_SIZE = 30 |
| # FIXME: This limit is for performance reasons. Remove when refactoring |
| # for performance tuning was successful. |
| IPV4_NETWORK_MAX_SIZE = 16 |
| IPV4_NETWORK_MIN_NUM_HOSTS = _ComputeIpv4NumHosts(IPV4_NETWORK_MIN_SIZE) |
| IPV4_NETWORK_MAX_NUM_HOSTS = _ComputeIpv4NumHosts(IPV4_NETWORK_MAX_SIZE) |
| |
| |
| class AddressPool(object): |
| """Address pool class, wrapping an C{objects.Network} object. |
| |
| This class provides methods to manipulate address pools, backed by |
| L{objects.Network} objects. |
| |
| """ |
| FREE = bitarray("0") |
| RESERVED = bitarray("1") |
| |
| def __init__(self, network): |
| """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) |
| else: |
| self.reservations = bitarray(self.network.numhosts) |
| # pylint: disable=E1103 |
| self.reservations.setall(False) |
| |
| 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) |
| |
| assert len(self.reservations) == self.network.numhosts |
| assert len(self.ext_reservations) == self.network.numhosts |
| |
| def Contains(self, address): |
| if address is None: |
| return False |
| addr = ipaddr.IPAddress(address) |
| |
| return addr in self.network |
| |
| def _GetAddrIndex(self, address): |
| addr = ipaddr.IPAddress(address) |
| |
| if not addr in self.network: |
| raise errors.AddressPoolError("%s does not contain %s" % |
| (self.network, addr)) |
| |
| return int(addr) - int(self.network.network) |
| |
| def Update(self): |
| """Write address pools back to the network object. |
| |
| """ |
| # pylint: disable=E1103 |
| self.net.ext_reservations = self.ext_reservations.to01() |
| self.net.reservations = self.reservations.to01() |
| |
| def _Mark(self, address, value=True, external=False): |
| idx = self._GetAddrIndex(address) |
| if external: |
| self.ext_reservations[idx] = value |
| else: |
| self.reservations[idx] = value |
| self.Update() |
| |
| def _GetSize(self): |
| return 2 ** (32 - self.network.prefixlen) |
| |
| @property |
| 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() |
| 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 or self.gateway6.is_link_local |
| |
| return True |
| |
| def IsFull(self): |
| """Check whether the network is full. |
| |
| """ |
| return self.all_reservations.all() |
| |
| def GetReservedCount(self): |
| """Get the count of reserved addresses. |
| |
| """ |
| return self.all_reservations.count(True) |
| |
| def GetFreeCount(self): |
| """Get the count of unused addresses. |
| |
| """ |
| return self.all_reservations.count(False) |
| |
| def GetMap(self): |
| """Return a textual representation of the network's occupation status. |
| |
| """ |
| return self.all_reservations.to01().replace("1", "X").replace("0", ".") |
| |
| def IsReserved(self, address): |
| """Checks if the given IP is reserved. |
| |
| """ |
| idx = self._GetAddrIndex(address) |
| return self.all_reservations[idx] |
| |
| def Reserve(self, address, external=False): |
| """Mark an address as used. |
| |
| """ |
| if self.IsReserved(address): |
| raise errors.AddressPoolError("%s is already reserved" % address) |
| self._Mark(address, external=external) |
| |
| def Release(self, address, external=False): |
| """Release a given address reservation. |
| |
| """ |
| self._Mark(address, value=False, external=external) |
| |
| def GetFreeAddress(self): |
| """Returns the first available address. |
| |
| """ |
| if self.IsFull(): |
| raise errors.AddressPoolError("%s is full" % self.network) |
| |
| idx = self.all_reservations.index(False) |
| address = str(self.network[idx]) |
| self.Reserve(address) |
| return address |
| |
| def GenerateFree(self): |
| """Returns the first free address of the network. |
| |
| @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. |
| |
| Reserve the network, broadcast and gateway IP addresses. |
| |
| """ |
| 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 |