Example #1
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_address,
                       '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'])
Example #2
0
    def _choose_available_subnet(self,
                                 context,
                                 net_id,
                                 version=None,
                                 ip_address=None):
        filters = {}
        if version:
            filters["version"] = version
        subnets = db_api.subnet_find_allocation_counts(context,
                                                       net_id,
                                                       scope=db_api.ALL,
                                                       **filters)
        for subnet, ips_in_subnet in subnets:
            ipnet = netaddr.IPNetwork(subnet["cidr"])
            if ip_address and ip_address not in ipnet:
                continue

            ip_policy_rules = None
            if not ip_address:
                ip_policy_rules = self.get_ip_policy_rule_set(subnet)
            policy_size = ip_policy_rules.size if ip_policy_rules else 0
            if ipnet.size > (ips_in_subnet + policy_size):
                return subnet

        raise exceptions.IpAddressGenerationFailure(net_id=net_id)
Example #3
0
    def _choose_available_subnet(self, context, net_id, version=None,
                                 segment_id=None, ip_address=None,
                                 reallocated_ips=None):
        subnets = super(QuarkIpamBOTHREQ, self)._choose_available_subnet(
            context, net_id, version, segment_id, ip_address, reallocated_ips)

        if len(reallocated_ips) + len(subnets) < 2:
            raise exceptions.IpAddressGenerationFailure(net_id=net_id)
        return subnets
Example #4
0
 def _choose_available_subnet(self, context, net_id, version=None,
                              segment_id=None, ip_address=None,
                              reallocated_ips=None):
     filters = {}
     if version:
         filters["ip_version"] = version
     subnet = self.select_subnet(context, net_id, ip_address, segment_id,
                                 **filters)
     if subnet:
         return [subnet]
     raise exceptions.IpAddressGenerationFailure(net_id=net_id)
Example #5
0
    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 _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'])
Example #7
0
 def _allocate_floatingip_from_configured_subnets(self, context):
     cfg.CONF.register_opts(explicit_floating_ip_opts, group='akanda')
     # NOTE(dhellmann): There may be a better way to do this, but
     # the "filter" argument to get_subnets() is not documented so
     # who knows.
     e_context = context.elevated()
     subnets = [
         self._get_subnet(e_context, unicode(s))
         for s in cfg.CONF.akanda.floatingip_subnet
     ]
     if not subnets:
         LOG.error('config setting akanda.floatingip_subnet missing')
         raise q_exc.IpAddressGenerationFailure(net_id='UNKNOWN')
     # The base class method _generate_ip() handles the allocation
     # ranges and going from one subnet to the next when a network
     # is exhausted.
     return self._generate_ip(context, subnets)
Example #8
0
    def _choose_available_subnet(self, context, net_id, version=None,
                                 segment_id=None, ip_address=None,
                                 reallocated_ips=None):
        both_subnet_versions = []
        need_versions = [4, 6]
        for i in reallocated_ips:
            if i["version"] in need_versions:
                need_versions.remove(i["version"])
        filters = {}
        for ver in need_versions:
            filters["ip_version"] = ver
            sub = self.select_subnet(context, net_id, ip_address, segment_id,
                                     **filters)
            if sub:
                both_subnet_versions.append(sub)
        if not reallocated_ips and not both_subnet_versions:
            raise exceptions.IpAddressGenerationFailure(net_id=net_id)

        return both_subnet_versions
 def test_create_port_catch_ip_generation_failure_reraise(self):
     self.assertRaises(
         n_exc.IpAddressGenerationFailure,
         self._test__port_action_with_failures,
         exc=n_exc.IpAddressGenerationFailure(net_id='foo_network_id'),
         action='create_port')
Example #10
0
    def _allocate_from_v6_subnet(self, context, net_id, subnet,
                                 port_id, reuse_after, ip_address=None,
                                 **kwargs):
        """This attempts to allocate v6 addresses as per RFC2462 and RFC3041.

        To accomodate this, we effectively treat all v6 assignment as a
        first time allocation utilizing the MAC address of the VIF. Because
        we recycle MACs, we will eventually attempt to recreate a previously
        generated v6 address. Instead of failing, we've opted to handle
        reallocating that address in this method.

        This should provide a performance boost over attempting to check
        each and every subnet in the existing reallocate logic, as we'd
        have to iterate over each and every subnet returned
        """

        LOG.info("Attempting to allocate a v6 address - [{0}]".format(
            utils.pretty_kwargs(network_id=net_id, subnet=subnet,
                                port_id=port_id, ip_address=ip_address)))

        if ip_address:
            LOG.info("IP %s explicitly requested, deferring to standard "
                     "allocation" % ip_address)
            return self._allocate_from_subnet(context, net_id=net_id,
                                              subnet=subnet, port_id=port_id,
                                              reuse_after=reuse_after,
                                              ip_address=ip_address, **kwargs)
        else:
            mac = kwargs.get("mac_address")
            if mac:
                mac = kwargs["mac_address"].get("address")

            ip_policy_cidrs = models.IPPolicy.get_ip_policy_cidrs(subnet)
            for tries, ip_address in enumerate(
                    generate_v6(mac, port_id, subnet["cidr"])):

                LOG.info("Attempt {0} of {1}".format(
                    tries + 1, CONF.QUARK.v6_allocation_attempts))

                if tries > CONF.QUARK.v6_allocation_attempts - 1:
                    LOG.info("Exceeded v6 allocation attempts, bailing")
                    raise exceptions.IpAddressGenerationFailure(
                        net_id=net_id)

                ip_address = netaddr.IPAddress(ip_address).ipv6()
                LOG.info("Generated a new v6 address {0}".format(
                    str(ip_address)))

                # NOTE(mdietz): treating the IPSet as a boolean caused netaddr
                #               to attempt to enumerate the entire set!
                if (ip_policy_cidrs is not None and
                        ip_address in ip_policy_cidrs):
                    LOG.info("Address {0} excluded by policy".format(
                        str(ip_address)))
                    continue

                # TODO(mdietz): replace this with a compare-and-swap loop
                with context.session.begin():
                    address = db_api.ip_address_find(
                        context, network_id=net_id, ip_address=ip_address,
                        scope=db_api.ONE, reuse_after=reuse_after,
                        deallocated=True, subnet_id=subnet["id"],
                        lock_mode=True)

                    if address:
                        LOG.info("Address {0} exists, claiming".format(
                            str(ip_address)))
                        return db_api.ip_address_update(
                            context, address, deallocated=False,
                            deallocated_at=None,
                            used_by_tenant_id=context.tenant_id,
                            allocated_at=timeutils.utcnow(),
                            address_type=kwargs.get('address_type',
                                                    ip_types.FIXED))

                # This triggers when the IP is allocated to another tenant,
                # either because we missed it due to our filters above, or
                # in an extremely unlikely race between the find and here.
                try:
                    with context.session.begin():
                        return db_api.ip_address_create(
                            context, address=ip_address,
                            subnet_id=subnet["id"],
                            version=subnet["ip_version"], network_id=net_id,
                            address_type=kwargs.get('address_type',
                                                    ip_types.FIXED))
                except db_exception.DBDuplicateEntry:
                    LOG.info("{0} exists but was already "
                             "allocated".format(str(ip_address)))
                    LOG.debug("Duplicate entry found when inserting subnet_id"
                              " %s ip_address %s", subnet["id"], ip_address)
Example #11
0
    def allocate_ip_address(self,
                            context,
                            net_id,
                            port_id,
                            reuse_after,
                            version=None,
                            ip_address=None):
        elevated = context.elevated()
        if ip_address:
            ip_address = netaddr.IPAddress(ip_address)

        address = db_api.ip_address_find(elevated,
                                         network_id=net_id,
                                         reuse_after=reuse_after,
                                         deallocated=True,
                                         scope=db_api.ONE,
                                         ip_address=ip_address)
        if address:
            return db_api.ip_address_update(elevated,
                                            address,
                                            deallocated=False,
                                            deallocated_at=None)

        subnet = self._choose_available_subnet(elevated,
                                               net_id,
                                               ip_address=ip_address,
                                               version=version)
        ip_policy_rules = self.get_ip_policy_rule_set(subnet)

        # Creating this IP for the first time
        next_ip = None
        if ip_address:
            next_ip = ip_address
            address = db_api.ip_address_find(elevated,
                                             network_id=net_id,
                                             ip_address=next_ip,
                                             tenant_id=elevated.tenant_id,
                                             scope=db_api.ONE)
            if address:
                raise exceptions.IpAddressGenerationFailure(net_id=net_id)
        else:
            address = True
            while address:
                next_ip_int = int(subnet["next_auto_assign_ip"])
                next_ip = netaddr.IPAddress(next_ip_int)
                if subnet["ip_version"] == 4:
                    next_ip = next_ip.ipv4()
                subnet["next_auto_assign_ip"] = next_ip_int + 1
                if ip_policy_rules and next_ip in ip_policy_rules:
                    continue
                address = db_api.ip_address_find(elevated,
                                                 network_id=net_id,
                                                 ip_address=next_ip,
                                                 tenant_id=elevated.tenant_id,
                                                 scope=db_api.ONE)

        address = db_api.ip_address_create(elevated,
                                           address=next_ip,
                                           subnet_id=subnet["id"],
                                           version=subnet["ip_version"],
                                           network_id=net_id)
        address["deallocated"] = 0

        return address
Example #12
0
    def allocate_ip_address(self, context, new_addresses, net_id, port_id,
                            reuse_after, segment_id=None, version=None,
                            ip_addresses=None, subnets=None, **kwargs):
        elevated = context.elevated()
        subnets = subnets or []
        ip_addresses = ip_addresses or []

        ipam_log = kwargs.get('ipam_log', None)
        LOG.info("Starting a new IP address(es) allocation. Strategy "
                 "is {0} - [{1}]".format(
                     self.get_name(),
                     utils.pretty_kwargs(network_id=net_id, port_id=port_id,
                                         new_addresses=new_addresses,
                                         ip_addresses=ip_addresses,
                                         subnets=subnets,
                                         segment_id=segment_id,
                                         version=version)))

        def _try_reallocate_ip_address(ipam_log, ip_addr=None):
            new_addresses.extend(self.attempt_to_reallocate_ip(
                context, net_id, port_id, reuse_after, version=None,
                ip_address=ip_addr, segment_id=segment_id, subnets=subnets,
                **kwargs))

        def _try_allocate_ip_address(ipam_log, ip_addr=None, sub=None):
            for retry in xrange(CONF.QUARK.ip_address_retry_max):
                attempt = None
                if ipam_log:
                    attempt = ipam_log.make_entry("_try_allocate_ip_address")
                LOG.info("Allocating new IP attempt {0} of {1}".format(
                    retry + 1, CONF.QUARK.ip_address_retry_max))
                if not sub:
                    subnets = self._choose_available_subnet(
                        elevated, net_id, version, segment_id=segment_id,
                        ip_address=ip_addr, reallocated_ips=new_addresses)
                else:
                    subnets = [self.select_subnet(context, net_id,
                                                  ip_addr, segment_id,
                                                  subnet_ids=[sub])]
                LOG.info("Subnet selection returned {0} viable subnet(s) - "
                         "IDs: {1}".format(len(subnets),
                                           ", ".join([str(s["id"])
                                                      for s in subnets if s])))

                try:
                    self._allocate_ips_from_subnets(context, new_addresses,
                                                    net_id, subnets,
                                                    port_id, reuse_after,
                                                    ip_addr, **kwargs)
                except q_exc.IPAddressRetryableFailure:
                    LOG.exception("Error in allocating IP")
                    if attempt:
                        LOG.debug("ATTEMPT FAILED")
                        attempt.failed()
                    remaining = CONF.QUARK.ip_address_retry_max - retry - 1
                    if remaining > 0:
                        LOG.info("{0} retries remain, retrying...".format(
                            remaining))
                    else:
                        LOG.info("No retries remaing, bailing")
                    continue
                finally:
                    if attempt:
                        attempt.end()

                break

        ip_addresses = [netaddr.IPAddress(ip_address)
                        for ip_address in ip_addresses]

        if ip_addresses:
            for ip_address in ip_addresses:
                _try_reallocate_ip_address(ipam_log, ip_address)
        else:
            _try_reallocate_ip_address(ipam_log)

        if self.is_strategy_satisfied(new_addresses):
            return
        else:
            LOG.info("Reallocated addresses {0} but still need more addresses "
                     "to satisfy strategy {1}. Falling back to creating "
                     "IPs".format(new_addresses, self.get_name()))

        if ip_addresses or subnets:
            for ip_address, subnet in itertools.izip_longest(ip_addresses,
                                                             subnets):
                _try_allocate_ip_address(ipam_log, ip_address, subnet)
        else:
            _try_allocate_ip_address(ipam_log)

        if self.is_strategy_satisfied(new_addresses, allocate_complete=True):
            self._notify_new_addresses(context, new_addresses)
            LOG.info("IPAM for port ID {0} completed with addresses "
                     "{1}".format(port_id,
                                  [a["address_readable"]
                                   for a in new_addresses]))
            return
        ipam_log.failed()

        raise exceptions.IpAddressGenerationFailure(net_id=net_id)
Example #13
0
    def allocate_ip_address(self,
                            context,
                            new_addresses,
                            net_id,
                            port_id,
                            reuse_after,
                            segment_id=None,
                            version=None,
                            ip_address=None,
                            subnets=None,
                            **kwargs):
        elevated = context.elevated()
        if ip_address:
            ip_address = netaddr.IPAddress(ip_address)

        new_addresses.extend(
            self.attempt_to_reallocate_ip(context,
                                          net_id,
                                          port_id,
                                          reuse_after,
                                          version=None,
                                          ip_address=ip_address,
                                          segment_id=segment_id,
                                          subnets=subnets,
                                          **kwargs))

        if self.is_strategy_satisfied(new_addresses):
            return

        for retry in xrange(cfg.CONF.QUARK.ip_address_retry_max):
            if not subnets:
                subs = self._choose_available_subnet(
                    elevated,
                    net_id,
                    version,
                    segment_id=segment_id,
                    ip_address=ip_address,
                    reallocated_ips=new_addresses)
            else:
                subs = [
                    self.select_subnet(context,
                                       net_id,
                                       ip_address,
                                       segment_id,
                                       subnet_ids=subnets)
                ]

            try:
                self._allocate_ips_from_subnets(context, new_addresses, net_id,
                                                subs, port_id, reuse_after,
                                                ip_address, **kwargs)
            except q_exc.IPAddressRetryableFailure:
                LOG.exception("Error in allocating IP")
                continue

            break

        if self.is_strategy_satisfied(new_addresses, allocate_complete=True):
            self._notify_new_addresses(context, new_addresses)
            return

        raise exceptions.IpAddressGenerationFailure(net_id=net_id)
Example #14
0
    def _allocate_from_v6_subnet(self,
                                 context,
                                 net_id,
                                 subnet,
                                 port_id,
                                 reuse_after,
                                 ip_address=None,
                                 **kwargs):
        """This attempts to allocate v6 addresses as per RFC2462 and RFC3041.

        To accomodate this, we effectively treat all v6 assignment as a
        first time allocation utilizing the MAC address of the VIF. Because
        we recycle MACs, we will eventually attempt to recreate a previously
        generated v6 address. Instead of failing, we've opted to handle
        reallocating that address in this method.

        This should provide a performance boost over attempting to check
        each and every subnet in the existing reallocate logic, as we'd
        have to iterate over each and every subnet returned
        """

        if not (ip_address is None and "mac_address" in kwargs
                and kwargs["mac_address"]):
            return self._allocate_from_subnet(context, net_id, subnet,
                                              reuse_after, ip_address,
                                              **kwargs)
        else:
            ip_policy_cidrs = models.IPPolicy.get_ip_policy_cidrs(subnet)
            for tries, ip_address in enumerate(
                    generate_v6(kwargs["mac_address"]["address"], port_id,
                                subnet["cidr"])):

                if tries > CONF.QUARK.v6_allocation_attempts - 1:
                    raise exceptions.IpAddressGenerationFailure(net_id=net_id)

                ip_address = netaddr.IPAddress(ip_address)

                # NOTE(mdietz): treating the IPSet as a boolean caused netaddr
                #               to attempt to enumerate the entire set!
                if (ip_policy_cidrs is not None
                        and ip_address in ip_policy_cidrs):
                    continue

                # TODO(mdietz): replace this with a compare-and-swap loop
                with context.session.begin():
                    address = db_api.ip_address_find(context,
                                                     network_id=net_id,
                                                     ip_address=ip_address,
                                                     scope=db_api.ONE,
                                                     reuse_after=reuse_after,
                                                     deallocated=True,
                                                     subnet_id=subnet["id"],
                                                     lock_mode=True)

                    if address:
                        return db_api.ip_address_update(
                            context,
                            address,
                            deallocated=False,
                            deallocated_at=None,
                            used_by_tenant_id=context.tenant_id,
                            allocated_at=timeutils.utcnow())

                # This triggers when the IP is allocated to another tenant,
                # either because we missed it due to our filters above, or
                # in an extremely unlikely race between the find and here.
                try:
                    with context.session.begin():
                        return db_api.ip_address_create(
                            context,
                            address=ip_address,
                            subnet_id=subnet["id"],
                            version=subnet["ip_version"],
                            network_id=net_id)
                except db_exception.DBDuplicateEntry:
                    LOG.debug(
                        "Duplicate entry found when inserting subnet_id"
                        " %s ip_address %s", subnet["id"], ip_address)
Example #15
0
    def attempt_to_reallocate_ip(self,
                                 context,
                                 net_id,
                                 port_id,
                                 reuse_after,
                                 version=None,
                                 ip_address=None,
                                 segment_id=None,
                                 subnets=None,
                                 **kwargs):
        version = version or [4, 6]
        elevated = context.elevated()

        if version == 6 and "mac_address" in kwargs and kwargs["mac_address"]:
            # Defers to the create case. The reason why is we'd have to look
            # up subnets here to correctly generate the v6. If we split them
            # up into reallocate and create, we'd be looking up the same
            # subnets twice, which is a waste of time.

            # TODO(mdietz): after reviewing this code, this block annoyingly
            #               doesn't trigger in the ANY case, since we end up
            #               using a list of [4, 6]. It works as expected most
            #               of the time, but we can anticipate that isolated
            #               networks will end up using sequential assignment.
            #               Probably want to rework this logic to compensate
            #               at some point. Considering they all come from the
            #               same MAC address pool, nothing bad will happen,
            #               just worth noticing and fixing.
            return []

        sub_ids = []
        if subnets:
            sub_ids = subnets
        else:
            if segment_id:
                subnets = db_api.subnet_find(elevated,
                                             network_id=net_id,
                                             segment_id=segment_id)
                sub_ids = [s["id"] for s in subnets]
                if not sub_ids:
                    raise exceptions.IpAddressGenerationFailure(net_id=net_id)

        ip_kwargs = {
            "network_id": net_id,
            "reuse_after": reuse_after,
            "deallocated": True,
            "scope": db_api.ONE,
            "ip_address": ip_address,
            "lock_mode": True,
            "version": version,
            "order_by": "address",
            "do_not_use": False
        }

        if sub_ids:
            ip_kwargs["subnet_id"] = sub_ids

        # We never want to take the chance of an infinite loop here. Instead,
        # we'll clean up multiple bad IPs if we find them (assuming something
        # is really wrong)
        for retry in xrange(cfg.CONF.QUARK.ip_address_retry_max):
            get_policy = models.IPPolicy.get_ip_policy_cidrs

            try:
                with context.session.begin():
                    # NOTE(mdietz): Before I removed the lazy=joined, this
                    #               raised with an unknown column "address"
                    #               error.
                    address = db_api.ip_address_find(elevated, **ip_kwargs)

                    if address:
                        # NOTE(mdietz): We should always be in the CIDR but we
                        #              also said that before :-/
                        subnet = address.get('subnet')
                        if subnet:
                            policy = get_policy(subnet)

                            cidr = netaddr.IPNetwork(address["subnet"]["cidr"])
                            addr = netaddr.IPAddress(int(address["address"]))
                            if address["subnet"]["ip_version"] == 4:
                                addr = addr.ipv4()
                            else:
                                addr = addr.ipv6()

                            if policy is not None and addr in policy:
                                context.session.delete(address)
                                continue

                            if addr in cidr:
                                updated_address = db_api.ip_address_update(
                                    elevated,
                                    address,
                                    deallocated=False,
                                    deallocated_at=None,
                                    used_by_tenant_id=context.tenant_id,
                                    allocated_at=timeutils.utcnow())
                                return [updated_address]
                            else:
                                # Make sure we never find it again
                                context.session.delete(address)
                    else:
                        break
            except Exception:
                LOG.exception("Error in reallocate ip...")
        return []
 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')
Example #17
0
    def attempt_to_reallocate_ip(self, context, net_id, port_id, reuse_after,
                                 version=None, ip_address=None,
                                 segment_id=None, subnets=None, **kwargs):
        version = version or [4, 6]
        elevated = context.elevated()

        LOG.info("Attempting to reallocate an IP (step 1 of 3) - [{0}]".format(
            utils.pretty_kwargs(network_id=net_id, port_id=port_id,
                                version=version, segment_id=segment_id,
                                subnets=subnets)))

        if version == 6:
            # Defers to the create case. The reason why is we'd have to look
            # up subnets here to correctly generate the v6. If we split them
            # up into reallocate and create, we'd be looking up the same
            # subnets twice, which is a waste of time.

            # TODO(mdietz): after reviewing this code, this block annoyingly
            #               doesn't trigger in the ANY case, since we end up
            #               using a list of [4, 6]. It works as expected most
            #               of the time, but we can anticipate that isolated
            #               networks will end up using sequential assignment.
            #               Probably want to rework this logic to compensate
            #               at some point. Considering they all come from the
            #               same MAC address pool, nothing bad will happen,
            #               just worth noticing and fixing.
            LOG.info("Identified as v6 case, deferring to IP create path")
            return []

        sub_ids = []
        if subnets:
            sub_ids = subnets
        elif segment_id:
            subnets = db_api.subnet_find(elevated,
                                         network_id=net_id,
                                         segment_id=segment_id)
            sub_ids = [s["id"] for s in subnets]
            if not sub_ids:
                LOG.info("No subnets matching segment_id {0} could be "
                         "found".format(segment_id))
                raise exceptions.IpAddressGenerationFailure(
                    net_id=net_id)

        ip_kwargs = {
            "network_id": net_id,
            "reuse_after": reuse_after,
            "deallocated": True,
            "ip_address": ip_address,
            "version": version,
        }
        if ip_address:
            del ip_kwargs["deallocated"]
        if sub_ids:
            ip_kwargs["subnet_id"] = sub_ids

        ipam_log = kwargs.get('ipam_log', None)

        for retry in xrange(CONF.QUARK.ip_address_retry_max):
            attempt = None
            if ipam_log:
                attempt = ipam_log.make_entry("attempt_to_reallocate_ip")
            LOG.info("Attempt {0} of {1}".format(
                retry + 1, CONF.QUARK.ip_address_retry_max))
            try:
                with context.session.begin():
                    transaction = db_api.transaction_create(context)
                m = models.IPAddress
                update_kwargs = {
                    m.transaction_id: transaction.id,
                    m.address_type: kwargs.get("address_type", ip_types.FIXED),
                    m.deallocated: False,
                    m.deallocated_at: None,
                    m.used_by_tenant_id: context.tenant_id,
                    m.allocated_at: timeutils.utcnow(),
                }
                result = db_api.ip_address_reallocate(
                    elevated, update_kwargs, **ip_kwargs)
                if not result:
                    LOG.info("Couldn't update any reallocatable addresses "
                             "given the criteria")
                    if attempt:
                        attempt.failed()
                    break

                updated_address = db_api.ip_address_reallocate_find(
                    elevated, transaction.id)
                if not updated_address:
                    if attempt:
                        attempt.failed()
                    continue

                LOG.info("Address {0} is reallocated".format(
                    updated_address["address_readable"]))
                return [updated_address]
            except Exception:
                if attempt:
                    attempt.failed()
                LOG.exception("Error in reallocate ip...")
            finally:
                if attempt:
                    attempt.end()
        return []
Example #18
0
    def _validate_subnet(self, context, s, cur_subnet=None):
        """Validate a subnet spec."""

        # This method will validate attributes which may change during
        # create_subnet() and update_subnet().
        # The method requires the subnet spec 's' has 'ip_version' field.
        # If 's' dict does not have 'ip_version' field in an API call
        # (e.g., update_subnet()), you need to set 'ip_version' field
        # before calling this method.

        ip_ver = s['ip_version']

        if attributes.is_attr_set(s.get('cidr')):
            self._validate_ip_version(ip_ver, s['cidr'], 'cidr')

        # TODO(watanabe.isao): After we found a way to avoid the re-sync
        # from the agent side, this restriction could be removed.
        if cur_subnet:
            dhcp_was_enabled = cur_subnet.enable_dhcp
        else:
            dhcp_was_enabled = False
        if s.get('enable_dhcp') and not dhcp_was_enabled:
            subnet_prefixlen = netaddr.IPNetwork(s['cidr']).prefixlen
            error_message = _("Subnet has a prefix length that is "
                              "incompatible with DHCP service enabled.")
            if ((ip_ver == 4 and subnet_prefixlen > 30)
                    or (ip_ver == 6 and subnet_prefixlen > 126)):
                raise n_exc.InvalidInput(error_message=error_message)
            else:
                # NOTE(watanabe.isao): The following restriction is necessary
                # only when updating subnet.
                if cur_subnet:
                    range_qry = context.session.query(
                        models_v2.IPAvailabilityRange).join(
                            models_v2.IPAllocationPool)
                    ip_range = range_qry.filter_by(subnet_id=s['id']).first()
                    if not ip_range:
                        raise n_exc.IpAddressGenerationFailure(
                            net_id=cur_subnet.network_id)

        if attributes.is_attr_set(s.get('gateway_ip')):
            self._validate_ip_version(ip_ver, s['gateway_ip'], 'gateway_ip')
            if (cfg.CONF.force_gateway_on_subnet
                    and not ipam.utils.check_gateway_in_subnet(
                        s['cidr'], s['gateway_ip'])):
                error_message = _("Gateway is not valid on subnet")
                raise n_exc.InvalidInput(error_message=error_message)
            # Ensure the gateway IP is not assigned to any port
            # skip this check in case of create (s parameter won't have id)
            # NOTE(salv-orlando): There is slight chance of a race, when
            # a subnet-update and a router-interface-add operation are
            # executed concurrently
            if cur_subnet:
                alloc_qry = context.session.query(models_v2.IPAllocation)
                allocated = alloc_qry.filter_by(
                    ip_address=cur_subnet['gateway_ip'],
                    subnet_id=cur_subnet['id']).first()
                if allocated and allocated['port_id']:
                    raise n_exc.GatewayIpInUse(
                        ip_address=cur_subnet['gateway_ip'],
                        port_id=allocated['port_id'])

        if attributes.is_attr_set(s.get('dns_nameservers')):
            if len(s['dns_nameservers']) > cfg.CONF.max_dns_nameservers:
                raise n_exc.DNSNameServersExhausted(
                    subnet_id=s.get('id', _('new subnet')),
                    quota=cfg.CONF.max_dns_nameservers)
            for dns in s['dns_nameservers']:
                try:
                    netaddr.IPAddress(dns)
                except Exception:
                    raise n_exc.InvalidInput(
                        error_message=(_("Error parsing dns address %s") %
                                       dns))
                self._validate_ip_version(ip_ver, dns, 'dns_nameserver')

        if attributes.is_attr_set(s.get('host_routes')):
            if len(s['host_routes']) > cfg.CONF.max_subnet_host_routes:
                raise n_exc.HostRoutesExhausted(
                    subnet_id=s.get('id', _('new subnet')),
                    quota=cfg.CONF.max_subnet_host_routes)
            # check if the routes are all valid
            for rt in s['host_routes']:
                self._validate_host_route(rt, ip_ver)

        if ip_ver == 4:
            if attributes.is_attr_set(s.get('ipv6_ra_mode')):
                raise n_exc.InvalidInput(
                    error_message=(_("ipv6_ra_mode is not valid when "
                                     "ip_version is 4")))
            if attributes.is_attr_set(s.get('ipv6_address_mode')):
                raise n_exc.InvalidInput(
                    error_message=(_("ipv6_address_mode is not valid when "
                                     "ip_version is 4")))
        if ip_ver == 6:
            self._validate_ipv6_attributes(s, cur_subnet)