def create_floatingip(self, context, floatingip): fip = floatingip['floatingip'] # Verify that subnet is not a SNAT host-pool self._md.check_floatingip_external_address(context, fip) with context.session.begin(subtransactions=True): if fip.get('subnet_id') or fip.get('floating_ip_address'): result = super(ApicL3Plugin, self).create_floatingip( context, floatingip) else: # Iterate over non SNAT host-pool subnets and try to allocate # an address other_subs = self._md.get_subnets_for_fip(context, fip) result = None for ext_sn in other_subs: fip['subnet_id'] = ext_sn try: with context.session.begin(nested=True): result = (super(ApicL3Plugin, self) .create_floatingip(context, floatingip)) break except exceptions.IpAddressGenerationFailure: LOG.info(_LI('No more floating IP addresses available ' 'in subnet %s'), ext_sn) if not result: raise exceptions.IpAddressGenerationFailure( net_id=fip['floating_network_id']) self._md.create_floatingip(context, result) self.update_floatingip_status(context, result['id'], result['status']) return result
def _try_generate_ip(context, subnets): """Generate an IP address. The IP address will be generated from one of the subnets defined on the network. """ range_qry = context.session.query( models_v2.IPAvailabilityRange).join( models_v2.IPAllocationPool).with_lockmode('update') for subnet in subnets: ip_range = range_qry.filter_by(subnet_id=subnet['id']).first() if not ip_range: LOG.debug("All IPs from subnet %(subnet_id)s (%(cidr)s) " "allocated", {'subnet_id': subnet['id'], 'cidr': subnet['cidr']}) continue ip_address = ip_range['first_ip'] if ip_range['first_ip'] == ip_range['last_ip']: # No more free indices on subnet => delete LOG.debug("No more free IP's in slice. Deleting " "allocation pool.") context.session.delete(ip_range) else: # increment the first free new_first_ip = str(netaddr.IPAddress(ip_address) + 1) ip_range['first_ip'] = new_first_ip LOG.debug("Allocated IP - %(ip_address)s from %(first_ip)s " "to %(last_ip)s", {'ip_address': ip_address, 'first_ip': ip_range['first_ip'], 'last_ip': ip_range['last_ip']}) return {'ip_address': ip_address, 'subnet_id': subnet['id']} raise n_exc.IpAddressGenerationFailure(net_id=subnets[0]['network_id'])
def _ipam_allocate_ips(self, context, ipam_driver, port, ips, revert_on_fail=True): """Allocate set of ips over IPAM. If any single ip allocation fails, tries to deallocate all allocated ip addresses. """ allocated = [] # we need to start with entries that asked for a specific IP in case # those IPs happen to be next in the line for allocation for ones that # didn't ask for a specific IP ips.sort(key=lambda x: 'ip_address' not in x) try: for ip in ips: # By default IP info is dict, used to allocate single ip # from single subnet. # IP info can be list, used to allocate single ip from # multiple subnets ip_list = [ip] if isinstance(ip, dict) else ip subnets = [ip_dict['subnet_id'] for ip_dict in ip_list] try: factory = ipam_driver.get_address_request_factory() ip_request = factory.get_request(context, port, ip_list[0]) ipam_allocator = ipam_driver.get_allocator(subnets) ip_address, subnet_id = ipam_allocator.allocate(ip_request) except ipam_exc.IpAddressGenerationFailureAllSubnets: raise n_exc.IpAddressGenerationFailure( net_id=port['network_id']) allocated.append({ 'ip_address': ip_address, 'subnet_id': subnet_id }) except Exception: with excutils.save_and_reraise_exception(): LOG.debug("An exception occurred during IP allocation.") if revert_on_fail and allocated: LOG.debug("Reverting allocation") # In case of deadlock deallocation fails with db error # and rewrites original exception preventing db_retry # wrappers from restarting entire api request. self._safe_rollback(self._ipam_deallocate_ips, context, ipam_driver, port, allocated, revert_on_fail=False) elif not revert_on_fail and ips: addresses = ', '.join(self._get_failed_ips(ips, allocated)) LOG.error( _LE("IP allocation failed on " "external system for %s"), addresses) return allocated
def test_create_port_catch_and_handle_ip_generation_failure(self): self.plugin.get_subnet.side_effect = (exceptions.SubnetNotFound( subnet_id='foo_subnet_id')) self._test__port_action_with_failures( exc=exceptions.IpAddressGenerationFailure(net_id='foo_network_id'), action='create_port') self._test__port_action_with_failures( exc=exceptions.InvalidInput(error_message='sorry'), action='create_port')
def _generate_ip(context, subnets, filtered_ips=None, prefer_next=False): """Generate an IP address. The IP address will be generated from one of the subnets defined on the network. """ filtered_ips = filtered_ips or [] subnet_id_list = [subnet['id'] for subnet in subnets] pool_qry = context.session.query(models_v2.IPAllocationPool) pool_qry = pool_qry.filter( models_v2.IPAllocationPool.subnet_id.in_(subnet_id_list)) allocation_qry = context.session.query(models_v2.IPAllocation) allocation_qry = allocation_qry.filter( models_v2.IPAllocation.subnet_id.in_(subnet_id_list)) ip_allocations = collections.defaultdict(netaddr.IPSet) for ipallocation in allocation_qry: subnet_ip_allocs = ip_allocations[ipallocation.subnet_id] subnet_ip_allocs.add(netaddr.IPAddress(ipallocation.ip_address)) ip_pools = collections.defaultdict(netaddr.IPSet) for ip_pool in pool_qry: subnet_ip_pools = ip_pools[ip_pool.subnet_id] subnet_ip_pools.add( netaddr.IPRange(ip_pool.first_ip, ip_pool.last_ip)) for subnet_id in subnet_id_list: subnet_ip_pools = ip_pools[subnet_id] subnet_ip_allocs = ip_allocations[subnet_id] filter_set = netaddr.IPSet() for ip in filtered_ips: filter_set.add(netaddr.IPAddress(ip)) av_set = subnet_ip_pools.difference(subnet_ip_allocs) av_set = av_set.difference(filter_set) av_set_size = av_set.size if av_set_size == 0: continue # Compute a window size, select an index inside the window, then # select the IP address at the selected index within the window if prefer_next: window = 1 else: window = min(av_set_size, 10) ip_index = random.randint(1, window) candidate_ips = list(itertools.islice(av_set, ip_index)) if candidate_ips: allocated_ip = candidate_ips[-1] return { 'ip_address': str(allocated_ip), 'subnet_id': subnet_id } raise n_exc.IpAddressGenerationFailure(net_id=subnets[0]['network_id'])
def test_generate_ip_exhausted_pool(self): with mock.patch.object(non_ipam.IpamNonPluggableBackend, '_try_generate_ip') as generate: with mock.patch.object(non_ipam.IpamNonPluggableBackend, '_rebuild_availability_ranges') as rebuild: exception = n_exc.IpAddressGenerationFailure(net_id='n') # fail first call but not second generate.side_effect = [exception, None] non_ipam.IpamNonPluggableBackend._generate_ip('c', 's') self.assertEqual(2, generate.call_count) rebuild.assert_called_once_with('c', 's')
def create_floatingip(self, context, floatingip): fip = floatingip['floatingip'] # REVISIT: This ensure_tenant call probably isn't needed, as # FIPs don't map directly to any AIM resources. If it is # needed, it could me moved to the FLOATING_IP.BEFORE_CREATE # callback in rocky and newer. self._md.ensure_tenant(context, fip['tenant_id']) with db_api.CONTEXT_READER.using(context): # Verify that subnet is not a SNAT host-pool. This could # be done from a FLOATING_IP.PRECOMMIT_CREATE callback, # but that callback is made after a FIP port has been # allocated from the subnet. An exception would cause that # port to be deleted, but we are better off not trying to # allocate from the SNAT subnet in the first place. self._md.check_floatingip_external_address(context, fip) if fip.get('subnet_id') or fip.get('floating_ip_address'): result = super(ApicL3Plugin, self).create_floatingip(context, floatingip) else: # Iterate over non SNAT host-pool subnets and try to # allocate an address. with db_api.CONTEXT_READER.using(context): other_subs = self._md.get_subnets_for_fip(context, fip) result = None for ext_sn in other_subs: fip['subnet_id'] = ext_sn try: result = (super(ApicL3Plugin, self).create_floatingip( context, floatingip)) break except exceptions.IpAddressGenerationFailure: LOG.info( 'No more floating IP addresses available ' 'in subnet %s', ext_sn) if not result: raise exceptions.IpAddressGenerationFailure( net_id=fip['floating_network_id']) # REVISIT: Replace with FLOATING_IP.AFTER_UPDATE callback, # which is called after creation as well, in ocata and newer # (called inside transaction in newton)? self._md.create_floatingip(context, result) # REVISIT: Consider using FLOATING_IP.PRECOMMIT_UPDATE # callback, which is called after creation as well, in queens # and newer, or maybe just calling update_floatingip_status # from the MD's create_floatingip method. with db_api.CONTEXT_WRITER.using(context): self.update_floatingip_status(context, result['id'], result['status']) return result
def _ipam_allocate_single_ip(self, context, ipam_driver, port, subnets): """Allocates single ip from set of subnets Raises n_exc.IpAddressGenerationFailure if allocation failed for all subnets. """ for subnet in subnets: try: return [ self._ipam_try_allocate_ip(context, ipam_driver, port, subnet), subnet ] except ipam_exc.IpAddressGenerationFailure: continue raise n_exc.IpAddressGenerationFailure(net_id=port['network_id'])
def test_create_port_catch_ip_generation_failure_reraise(self): self.assertRaises( exceptions.IpAddressGenerationFailure, self._test__port_action_with_failures, exc=exceptions.IpAddressGenerationFailure(net_id='foo_network_id'), action='create_port')
def test_create_port_catch_and_handle_ip_generation_failure(self): self.plugin.get_subnet.side_effect = (n_exc.SubnetNotFound( subnet_id='foo_subnet_id')) self._test__port_action_with_failures( exc=n_exc.IpAddressGenerationFailure(net_id='foo_network_id'), action='create_port')
def ip_address_failure(network_id): if STRATEGY.is_provider_network(network_id): return q_exc.ProviderNetworkOutOfIps(net_id=network_id) else: return n_exc.IpAddressGenerationFailure(net_id=network_id)