def backend_allocate(self, address_request): try: # allocate a specific IP if isinstance(address_request, ipam_req.SpecificAddressRequest): # This handles both specific and automatic address requests ip_address = str(address_request.address) # If this is the subnet gateway IP - no need to allocate it subnet = self.get_details() if str(subnet.gateway_ip) == ip_address: LOG.info("Skip allocation of gateway-ip for pool %s", self._nsx_pool_id) return ip_address else: # Allocate any free IP ip_address = None response = self.nsxlib_ipam.allocate(self._nsx_pool_id, ip_addr=ip_address) ip_address = response['allocation_id'] except nsx_lib_exc.ManagerError as e: LOG.error( "NSX IPAM failed to allocate ip %(ip)s of subnet " "%(id)s: %(e)s; code %(code)s", { 'e': e, 'ip': ip_address, 'id': self._subnet_id, 'code': e.error_code }) if e.error_code == error.ERR_CODE_IPAM_POOL_EXHAUSTED: # No more IP addresses available on the pool raise ipam_exc.IpAddressGenerationFailure( subnet_id=self._subnet_id) if e.error_code == error.ERR_CODE_IPAM_SPECIFIC_IP: # The NSX backend does not support allocation of specific IPs # prior to version 2.0. msg = (_("NSX-V3 IPAM driver does not support allocation of a " "specific ip %s for port") % ip_address) raise NotImplementedError(msg) if e.error_code == error.ERR_CODE_IPAM_IP_ALLOCATED: # This IP is already in use raise ipam_exc.IpAddressAlreadyAllocated( ip=ip_address, subnet_id=self._subnet_id) if e.error_code == error.ERR_CODE_OBJECT_NOT_FOUND: msg = (_("NSX-V3 IPAM failed to allocate: pool %s was not " "found") % self._nsx_pool_id) raise ipam_exc.IpamValueInvalid(message=msg) else: # another backend error raise ipam_exc.IPAllocationFailed() except Exception as e: LOG.error( "NSX IPAM failed to allocate ip %(ip)s of subnet " "%(id)s: %(e)s", { 'e': e, 'ip': ip_address, 'id': self._subnet_id }) # handle unexpected failures raise ipam_exc.IPAllocationFailed() return ip_address
def backend_allocate(self, address_request): try: # allocate a specific IP if isinstance(address_request, ipam_req.SpecificAddressRequest): # This handles both specific and automatic address requests ip_address = str(address_request.address) self._vcns.allocate_ipam_ip_from_pool(self._nsx_pool_id, ip_addr=ip_address) else: # Allocate any free IP response = self._vcns.allocate_ipam_ip_from_pool( self._nsx_pool_id)[1] # get the ip from the response root = et.fromstring(response) ip_address = root.find('ipAddress').text except vc_exc.VcnsApiException as e: # handle backend failures error_code = self._get_vcns_error_code(e) if error_code == constants.NSX_ERROR_IPAM_ALLOCATE_IP_USED: # This IP is already in use raise ipam_exc.IpAddressAlreadyAllocated( ip=ip_address, subnet_id=self._subnet_id) if error_code == constants.NSX_ERROR_IPAM_ALLOCATE_ALL_USED: # No more IP addresses available on the pool raise ipam_exc.IpAddressGenerationFailure( subnet_id=self._subnet_id) else: raise ipam_exc.IPAllocationFailed() return ip_address
def delete_range(self, session, db_range): """Return count of deleted ranges :param session: database session :param db_range: IpamAvailabilityRange db object """ try: return session.query( db_models.IpamAvailabilityRange).filter_by( allocation_pool_id=db_range.allocation_pool_id).filter_by( first_ip=db_range.first_ip).filter_by( last_ip=db_range.last_ip).delete() except orm_exc.ObjectDeletedError: raise db_exc.RetryRequest(ipam_exc.IPAllocationFailed())
def update_range(self, session, db_range, first_ip=None, last_ip=None): """Updates db_range to have new first_ip and last_ip. :param session: database session :param db_range: IpamAvailabilityRange db object :param first_ip: first ip address in range :param last_ip: last ip address in range :return: count of updated rows """ opts = {} if first_ip: opts['first_ip'] = str(first_ip) if last_ip: opts['last_ip'] = str(last_ip) if not opts: raise ipam_exc.IpamAvailabilityRangeNoChanges() try: return session.query( db_models.IpamAvailabilityRange).filter_by( allocation_pool_id=db_range.allocation_pool_id).filter_by( first_ip=db_range.first_ip).filter_by( last_ip=db_range.last_ip).update(opts) except orm_exc.ObjectDeletedError: raise db_exc.RetryRequest(ipam_exc.IPAllocationFailed())
def _allocate_specific_ip(self, session, ip_address, allocation_pool_id=None, auto_generated=False): """Remove an IP address from subnet's availability ranges. This method is supposed to be called from within a database transaction, otherwise atomicity and integrity might not be enforced and the operation might result in incosistent availability ranges for the subnet. :param session: database session :param ip_address: ip address to mark as allocated :param allocation_pool_id: identifier of the allocation pool from which the ip address has been extracted. If not specified this routine will scan all allocation pools. :param auto_generated: indicates whether ip was auto generated :returns: list of IP ranges as instances of IPAvailabilityRange """ # Return immediately for EUI-64 addresses. For this # class of subnets availability ranges do not apply if ipv6_utils.is_eui64_address(ip_address): return LOG.debug( "Removing %(ip_address)s from availability ranges for " "subnet id:%(subnet_id)s", { 'ip_address': ip_address, 'subnet_id': self.subnet_manager.neutron_id }) # Netaddr's IPRange and IPSet objects work very well even with very # large subnets, including IPv6 ones. final_ranges = [] ip_in_pools = False if allocation_pool_id: av_ranges = self.subnet_manager.list_ranges_by_allocation_pool( session, allocation_pool_id) else: av_ranges = self.subnet_manager.list_ranges_by_subnet_id(session) for db_range in av_ranges: initial_ip_set = netaddr.IPSet( netaddr.IPRange(db_range['first_ip'], db_range['last_ip'])) final_ip_set = initial_ip_set - netaddr.IPSet([ip_address]) if not final_ip_set: ip_in_pools = True # Range exhausted - bye bye if not self.subnet_manager.delete_range(session, db_range): raise db_exc.RetryRequest(ipam_exc.IPAllocationFailed()) continue if initial_ip_set == final_ip_set: # IP address does not fall within the current range, move # to the next one final_ranges.append(db_range) continue ip_in_pools = True for new_range in final_ip_set.iter_ipranges(): # store new range in database # use netaddr.IPAddress format() method which is equivalent # to str(...) but also enables us to use different # representation formats (if needed) for IPv6. first_ip = netaddr.IPAddress(new_range.first) last_ip = netaddr.IPAddress(new_range.last) if (db_range['first_ip'] == first_ip.format() or db_range['last_ip'] == last_ip.format()): rows = self.subnet_manager.update_range(session, db_range, first_ip=first_ip, last_ip=last_ip) if not rows: raise db_exc.RetryRequest( ipam_exc.IPAllocationFailed()) LOG.debug("Adjusted availability range for pool %s", db_range['allocation_pool_id']) final_ranges.append(db_range) else: new_ip_range = self.subnet_manager.create_range( session, db_range['allocation_pool_id'], first_ip.format(), last_ip.format()) LOG.debug("Created availability range for pool %s", new_ip_range['allocation_pool_id']) final_ranges.append(new_ip_range) # If ip is autogenerated it should be present in allocation pools, # so retry if it is not there if auto_generated and not ip_in_pools: raise db_exc.RetryRequest(ipam_exc.IPAllocationFailed()) # Most callers might ignore this return value, which is however # useful for testing purposes LOG.debug( "Availability ranges for subnet id %(subnet_id)s " "modified: %(new_ranges)s", { 'subnet_id': self.subnet_manager.neutron_id, 'new_ranges': ", ".join([ "[%s; %s]" % (r['first_ip'], r['last_ip']) for r in final_ranges ]) }) return final_ranges