예제 #1
0
    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
예제 #2
0
    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'])
예제 #3
0
    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
예제 #4
0
 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')
예제 #5
0
    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')
예제 #7
0
 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
예제 #8
0
    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'])
예제 #9
0
 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')
예제 #10
0
 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')
예제 #11
0
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)