Example #1
0
    def select_subnet(self,
                      context,
                      net_id,
                      ip_address,
                      segment_id,
                      subnet_ids=None,
                      **filters):
        LOG.info("Selecting subnet(s) - (Step 2 of 3) [{0}]".format(
            utils.pretty_kwargs(network_id=net_id,
                                ip_address=ip_address,
                                segment_id=segment_id,
                                subnet_ids=subnet_ids,
                                ip_version=filters.get("ip_version"))))

        # TODO(mdietz): Invert the iterator and the session, should only be
        #               one subnet per attempt. We should also only be fetching
        #               the subnet and usage when we need to. Otherwise
        #               we're locking every subnet for a segment, and once
        #               we stop locking, we're looking at stale data.
        with context.session.begin():
            for subnet, ips_in_subnet in self._select_subnet(
                    context, net_id, ip_address, segment_id, subnet_ids,
                    **filters):
                if subnet is None:
                    continue
                ipnet = netaddr.IPNetwork(subnet["cidr"])
                LOG.info("Trying subnet ID: {0} - CIDR: {1}".format(
                    subnet["id"], subnet["_cidr"]))

                if not self._ip_in_subnet(subnet, subnet_ids, ipnet,
                                          ip_address):
                    continue

                if self._should_mark_subnet_full(context, subnet, ipnet,
                                                 ip_address, ips_in_subnet):
                    LOG.info("Marking subnet {0} as full".format(subnet["id"]))
                    updated = db_api.subnet_update_set_full(context, subnet)

                    # Ensure the session is aware of the changes to the subnet
                    if updated:
                        context.session.refresh(subnet)
                    continue

                if not ip_address and subnet["ip_version"] == 4:
                    auto_inc = db_api.subnet_update_next_auto_assign_ip
                    updated = auto_inc(context, subnet)

                    if updated:
                        context.session.refresh(subnet)
                    else:
                        # This means the subnet was marked full
                        # while we were checking out policies.
                        # Fall out and go back to the outer retry
                        # loop.
                        return

                LOG.info("Subnet {0} - {1} {2} looks viable, "
                         "returning".format(subnet["id"], subnet["_cidr"],
                                            subnet["next_auto_assign_ip"]))
                return subnet
Example #2
0
    def _allocate_ips_from_subnets(self, context, new_addresses, net_id,
                                   subnets, port_id, reuse_after,
                                   ip_address=None, **kwargs):

        LOG.info("Allocating IP(s) from chosen subnet(s) (step 3 of 3) - "
                 "[{0}]".format(utils.pretty_kwargs(
                     network_id=net_id, port_id=port_id,
                     new_addresses=new_addresses, ip_address=ip_address)))

        subnets = subnets or []
        for subnet in subnets:
            if not subnet:
                continue

            LOG.info("Attempting to allocate from {0} - {1}".format(
                subnet["id"], subnet["_cidr"]))

            address = None
            if int(subnet["ip_version"]) == 4:
                address = self._allocate_from_subnet(context, net_id,
                                                     subnet, port_id,
                                                     reuse_after,
                                                     ip_address, **kwargs)
            else:
                address = self._allocate_from_v6_subnet(context, net_id,
                                                        subnet, port_id,
                                                        reuse_after,
                                                        ip_address, **kwargs)
            if address:
                LOG.info("Created IP {0}".format(
                    address["address_readable"]))
                new_addresses.append(address)

        return new_addresses
Example #3
0
    def _allocate_ips_from_subnets(self, context, new_addresses, net_id,
                                   subnets, port_id, reuse_after,
                                   ip_address=None, **kwargs):

        LOG.info("Allocating IP(s) from chosen subnet(s) (step 3 of 3) - "
                 "[{0}]".format(utils.pretty_kwargs(
                     network_id=net_id, port_id=port_id,
                     new_addresses=new_addresses, ip_address=ip_address)))

        subnets = subnets or []
        for subnet in subnets:
            if not subnet:
                continue

            LOG.info("Attempting to allocate from {0} - {1}".format(
                subnet["id"], subnet["_cidr"]))

            address = None
            if int(subnet["ip_version"]) == 4:
                address = self._allocate_from_subnet(context, net_id,
                                                     subnet, port_id,
                                                     reuse_after,
                                                     ip_address, **kwargs)
            else:
                address = self._allocate_from_v6_subnet(context, net_id,
                                                        subnet, port_id,
                                                        reuse_after,
                                                        ip_address, **kwargs)
            if address:
                LOG.info("Created IP {0}".format(
                    address["address_readable"]))
                new_addresses.append(address)

        return new_addresses
Example #4
0
    def select_subnet(self, context, net_id, ip_address, segment_id,
                      subnet_ids=None, **filters):
        LOG.info("Selecting subnet(s) - (Step 2 of 3) [{0}]".format(
            utils.pretty_kwargs(network_id=net_id, ip_address=ip_address,
                                segment_id=segment_id, subnet_ids=subnet_ids,
                                ip_version=filters.get("ip_version"))))

        subnets = db_api.subnet_find_ordered_by_most_full(
            context, net_id, segment_id=segment_id, scope=db_api.ALL,
            subnet_id=subnet_ids, **filters)

        if not subnets:
            LOG.info("No subnets found given the search criteria!")

        for subnet, ips_in_subnet in subnets:
            ipnet = netaddr.IPNetwork(subnet["cidr"])
            LOG.info("Trying subnet ID: {0} - CIDR: {1}".format(
                subnet["id"], subnet["_cidr"]))
            if ip_address:
                na_ip = netaddr.IPAddress(ip_address)
                if ipnet.version == 4 and na_ip.version != 4:
                    na_ip = na_ip.ipv4()
                if na_ip not in ipnet:
                    if subnet_ids is not None:
                        LOG.info("Requested IP {0} not in subnet {1}, "
                                 "retrying".format(str(na_ip), str(ipnet)))
                        raise q_exc.IPAddressNotInSubnet(
                            ip_addr=ip_address, subnet_id=subnet["id"])
                    continue

            ip_policy = None
            if not ip_address:
                # Policies don't prevent explicit assignment, so we only
                # need to check if we're allocating a new IP
                ip_policy = subnet.get("ip_policy")

            policy_size = ip_policy["size"] if ip_policy else 0

            if ipnet.size > (ips_in_subnet + policy_size - 1):
                if not ip_address:
                    ip = subnet["next_auto_assign_ip"]
                    # If ip is somehow -1 in here don't touch it anymore
                    if ip != -1:
                        ip += 1
                    # and even then if it is outside the valid range set it to
                    # -1 to be safe
                    if ip < subnet["first_ip"] or ip > subnet["last_ip"]:
                        LOG.info("Marking subnet {0} as full".format(
                            subnet["id"]))
                        ip = -1
                    self._set_subnet_next_auto_assign_ip(context, subnet, ip)
                LOG.info("Subnet {0} - {1} looks viable, returning".format(
                    subnet["id"], subnet["_cidr"]))
                return subnet
            else:
                LOG.info("Marking subnet {0} as full".format(subnet["id"]))
                self._set_subnet_next_auto_assign_ip(context, subnet, -1)
Example #5
0
    def _allocate_from_subnet(self,
                              context,
                              net_id,
                              subnet,
                              port_id,
                              reuse_after,
                              ip_address=None,
                              **kwargs):

        LOG.info("Creating a new address in subnet {0} - [{1}]".format(
            subnet["_cidr"],
            utils.pretty_kwargs(network_id=net_id,
                                subnet=subnet,
                                port_id=port_id,
                                ip_address=ip_address)))

        if subnet and subnet["ip_policy"]:
            ip_policy_cidrs = subnet["ip_policy"].get_cidrs_ip_set()
        else:
            ip_policy_cidrs = netaddr.IPSet([])

        next_ip = ip_address
        if not next_ip:
            if subnet["next_auto_assign_ip"] != -1:
                next_ip = netaddr.IPAddress(subnet["next_auto_assign_ip"] - 1)
            else:
                next_ip = netaddr.IPAddress(subnet["last_ip"])
            if subnet["ip_version"] == 4:
                next_ip = next_ip.ipv4()

        LOG.info("Next IP is {0}".format(str(next_ip)))
        if ip_policy_cidrs and next_ip in ip_policy_cidrs and not ip_address:
            LOG.info("Next IP {0} violates policy".format(str(next_ip)))
            raise q_exc.IPAddressPolicyRetryableFailure(ip_addr=next_ip,
                                                        net_id=net_id)
        try:
            with context.session.begin():
                address = db_api.ip_address_create(
                    context,
                    address=next_ip,
                    subnet_id=subnet["id"],
                    deallocated=0,
                    version=subnet["ip_version"],
                    network_id=net_id,
                    port_id=port_id,
                    address_type=kwargs.get('address_type', ip_types.FIXED))
                address["deallocated"] = 0
                # alexm: instead of notifying billing from here we notify from
                # allocate_ip_address() when it's clear that the IP
                # allocation was successful
        except db_exception.DBDuplicateEntry:
            raise n_exc.IpAddressInUse(ip_address=next_ip, net_id=net_id)
        except db_exception.DBError:
            raise q_exc.IPAddressRetryableFailure(ip_addr=next_ip,
                                                  net_id=net_id)

        return address
Example #6
0
File: ipam.py Project: roaet/quark
    def select_subnet(self, context, net_id, ip_address, segment_id,
                      subnet_ids=None, **filters):
        LOG.info("Selecting subnet(s) - (Step 2 of 3) [{0}]".format(
            utils.pretty_kwargs(network_id=net_id, ip_address=ip_address,
                                segment_id=segment_id, subnet_ids=subnet_ids,
                                ip_version=filters.get("ip_version"))))

        # TODO(mdietz): Invert the iterator and the session, should only be
        #               one subnet per attempt. We should also only be fetching
        #               the subnet and usage when we need to. Otherwise
        #               we're locking every subnet for a segment, and once
        #               we stop locking, we're looking at stale data.
        with context.session.begin():
            for subnet, ips_in_subnet in self._select_subnet(context, net_id,
                                                             ip_address,
                                                             segment_id,
                                                             subnet_ids,
                                                             **filters):
                if subnet is None:
                    continue
                ipnet = netaddr.IPNetwork(subnet["cidr"])
                LOG.info("Trying subnet ID: {0} - CIDR: {1}".format(
                    subnet["id"], subnet["_cidr"]))

                if not self._ip_in_subnet(subnet, subnet_ids, ipnet,
                                          ip_address):
                    continue

                if self._should_mark_subnet_full(context, subnet, ipnet,
                                                 ip_address, ips_in_subnet):
                    LOG.info("Marking subnet {0} as full".format(subnet["id"]))
                    updated = db_api.subnet_update_set_full(context, subnet)

                    # Ensure the session is aware of the changes to the subnet
                    if updated:
                        context.session.refresh(subnet)
                    continue

                if not ip_address and subnet["ip_version"] == 4:
                    auto_inc = db_api.subnet_update_next_auto_assign_ip
                    updated = auto_inc(context, subnet)

                    if updated:
                        context.session.refresh(subnet)
                    else:
                        # This means the subnet was marked full
                        # while we were checking out policies.
                        # Fall out and go back to the outer retry
                        # loop.
                        return

                LOG.info("Subnet {0} - {1} {2} looks viable, "
                         "returning".format(subnet["id"], subnet["_cidr"],
                                            subnet["next_auto_assign_ip"]))
                return subnet
Example #7
0
    def _allocate_from_subnet(self,
                              context,
                              net_id,
                              subnet,
                              port_id,
                              reuse_after,
                              ip_address=None,
                              **kwargs):

        LOG.info("Creating a new address in subnet {0} - [{1}]".format(
            subnet["_cidr"],
            utils.pretty_kwargs(network_id=net_id,
                                subnet=subnet,
                                port_id=port_id,
                                ip_address=ip_address)))

        ip_policy_cidrs = models.IPPolicy.get_ip_policy_cidrs(subnet)
        next_ip = ip_address
        if not next_ip:
            if subnet["next_auto_assign_ip"] != -1:
                next_ip = netaddr.IPAddress(subnet["next_auto_assign_ip"] - 1)
            else:
                next_ip = netaddr.IPAddress(subnet["last_ip"])
            if subnet["ip_version"] == 4:
                next_ip = next_ip.ipv4()

        LOG.info("Next IP is {0}".format(str(next_ip)))
        if ip_policy_cidrs and next_ip in ip_policy_cidrs and not ip_address:
            LOG.info("Next IP {0} violates policy".format(str(next_ip)))
            raise q_exc.IPAddressPolicyRetryableFailure(ip_addr=next_ip,
                                                        net_id=net_id)
        try:
            with context.session.begin():
                address = db_api.ip_address_create(
                    context,
                    address=next_ip,
                    subnet_id=subnet["id"],
                    deallocated=0,
                    version=subnet["ip_version"],
                    network_id=net_id,
                    port_id=port_id,
                    address_type=kwargs.get('address_type', ip_types.FIXED))
                address["deallocated"] = 0
        except Exception:
            # NOTE(mdietz): Our version of sqlalchemy incorrectly raises None
            #               here when there's an IP conflict
            if ip_address:
                raise exceptions.IpAddressInUse(ip_address=next_ip,
                                                net_id=net_id)
            raise q_exc.IPAddressRetryableFailure(ip_addr=next_ip,
                                                  net_id=net_id)

        return address
Example #8
0
File: ipam.py Project: roaet/quark
    def _allocate_from_subnet(self, context, net_id, subnet,
                              port_id, reuse_after, ip_address=None, **kwargs):

        LOG.info("Creating a new address in subnet {0} - [{1}]".format(
            subnet["_cidr"], utils.pretty_kwargs(network_id=net_id,
                                                 subnet=subnet,
                                                 port_id=port_id,
                                                 ip_address=ip_address)))

        if subnet and subnet["ip_policy"]:
            ip_policy_cidrs = subnet["ip_policy"].get_cidrs_ip_set()
        else:
            ip_policy_cidrs = netaddr.IPSet([])

        next_ip = ip_address
        if not next_ip:
            if subnet["next_auto_assign_ip"] != -1:
                next_ip = netaddr.IPAddress(subnet["next_auto_assign_ip"] - 1)
            else:
                next_ip = netaddr.IPAddress(subnet["last_ip"])
            if subnet["ip_version"] == 4:
                next_ip = next_ip.ipv4()

        LOG.info("Next IP is {0}".format(str(next_ip)))
        if ip_policy_cidrs and next_ip in ip_policy_cidrs and not ip_address:
            LOG.info("Next IP {0} violates policy".format(str(next_ip)))
            raise q_exc.IPAddressPolicyRetryableFailure(ip_addr=next_ip,
                                                        net_id=net_id)
        try:
            with context.session.begin():
                address = db_api.ip_address_create(
                    context, address=next_ip, subnet_id=subnet["id"],
                    deallocated=0, version=subnet["ip_version"],
                    network_id=net_id,
                    port_id=port_id,
                    address_type=kwargs.get('address_type', ip_types.FIXED))
                address["deallocated"] = 0
                # alexm: instead of notifying billing from here we notify from
                # allocate_ip_address() when it's clear that the IP
                # allocation was successful
        except db_exception.DBDuplicateEntry:
            raise n_exc.IpAddressInUse(ip_address=next_ip, net_id=net_id)
        except db_exception.DBError:
            raise q_exc.IPAddressRetryableFailure(ip_addr=next_ip,
                                                  net_id=net_id)

        return address
Example #9
0
    def _allocate_from_subnet(self, context, net_id, subnet,
                              port_id, reuse_after, ip_address=None, **kwargs):

        LOG.info("Creating a new address in subnet {0} - [{1}]".format(
            subnet["_cidr"], utils.pretty_kwargs(network_id=net_id,
                                                 subnet=subnet,
                                                 port_id=port_id,
                                                 ip_address=ip_address)))

        ip_policy_cidrs = models.IPPolicy.get_ip_policy_cidrs(subnet)
        next_ip = ip_address
        if not next_ip:
            if subnet["next_auto_assign_ip"] != -1:
                next_ip = netaddr.IPAddress(subnet["next_auto_assign_ip"] - 1)
            else:
                next_ip = netaddr.IPAddress(subnet["last_ip"])
            if subnet["ip_version"] == 4:
                next_ip = next_ip.ipv4()

        LOG.info("Next IP is {0}".format(str(next_ip)))
        if ip_policy_cidrs and next_ip in ip_policy_cidrs and not ip_address:
            LOG.info("Next IP {0} violates policy".format(str(next_ip)))
            raise q_exc.IPAddressPolicyRetryableFailure(ip_addr=next_ip,
                                                        net_id=net_id)
        try:
            with context.session.begin():
                address = db_api.ip_address_create(
                    context, address=next_ip, subnet_id=subnet["id"],
                    deallocated=0, version=subnet["ip_version"],
                    network_id=net_id,
                    port_id=port_id,
                    address_type=kwargs.get('address_type', ip_types.FIXED))
                address["deallocated"] = 0
        except Exception:
            # NOTE(mdietz): Our version of sqlalchemy incorrectly raises None
            #               here when there's an IP conflict
            if ip_address:
                raise exceptions.IpAddressInUse(ip_address=next_ip,
                                                net_id=net_id)
            raise q_exc.IPAddressRetryableFailure(ip_addr=next_ip,
                                                  net_id=net_id)

        return address
Example #10
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 #11
0
File: ipam.py Project: roaet/quark
    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=version,
                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):
            # Only notify when all went well
            for address in new_addresses:
                billing.notify(context, billing.IP_ADD, address, **kwargs)
            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 ip_address_failure(net_id)
Example #12
0
File: ipam.py Project: roaet/quark
    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")

            if subnet and subnet["ip_policy"]:
                ip_policy_cidrs = subnet["ip_policy"].get_cidrs_ip_set()
            else:
                ip_policy_cidrs = netaddr.IPSet([])

            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 ip_address_failure(net_id)

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

                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

                try:
                    with context.session.begin():
                        address = 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))
                        return address
                except db_exception.DBDuplicateEntry:
                    # This shouldn't ever happen, since we hold a unique MAC
                    # address from the previous IPAM step.
                    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 #13
0
File: ipam.py Project: roaet/quark
    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, ip_address=ip_address)))

        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 ip_address_failure(net_id)

        ip_kwargs = {
            "network_id": net_id,
            "deallocated": True,
            "version": version,
            "lock_id": None,
        }
        if reuse_after is not None:
            ip_kwargs["reuse_after"] = reuse_after
        if ip_address is not None:
            ip_kwargs["ip_address"] = 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 #14
0
File: ipam.py Project: roaet/quark
    def allocate_mac_address(self, context, net_id, port_id, reuse_after,
                             mac_address=None,
                             use_forbidden_mac_range=False, **kwargs):
        if mac_address:
            mac_address = netaddr.EUI(mac_address).value

        kwargs.update({"network_id": net_id, "port_id": port_id,
                       "mac_address": mac_address,
                       "use_forbidden_mac_range": use_forbidden_mac_range})
        LOG.info(("Attempting to allocate a new MAC address "
                  "[{0}]").format(utils.pretty_kwargs(**kwargs)))

        for retry in xrange(CONF.QUARK.mac_address_retry_max):
            LOG.info("Attemping to reallocate deallocated MAC (step 1 of 3),"
                     " attempt {0} of {1}".format(
                         retry + 1, CONF.QUARK.mac_address_retry_max))
            try:
                with context.session.begin():
                    transaction = db_api.transaction_create(context)
                update_kwargs = {
                    "deallocated": False,
                    "deallocated_at": None,
                    "transaction_id": transaction.id
                }
                filter_kwargs = {
                    "deallocated": True,
                }
                if mac_address is not None:
                    filter_kwargs["address"] = mac_address
                if reuse_after is not None:
                    filter_kwargs["reuse_after"] = reuse_after
                elevated = context.elevated()
                result = db_api.mac_address_reallocate(
                    elevated, update_kwargs, **filter_kwargs)
                if not result:
                    break

                reallocated_mac = db_api.mac_address_reallocate_find(
                    elevated, transaction.id)
                if reallocated_mac:
                    dealloc = netaddr.EUI(reallocated_mac["address"])
                    LOG.info("Found a suitable deallocated MAC {0}".format(
                        str(dealloc)))
                    LOG.info("MAC assignment for port ID {0} completed "
                             "with address {1}".format(port_id, dealloc))
                    return reallocated_mac
            except Exception:
                LOG.exception("Error in mac reallocate...")
                continue

        LOG.info("Couldn't find a suitable deallocated MAC, attempting "
                 "to create a new one")

        # This could fail if a large chunk of MACs were chosen explicitly,
        # but under concurrent load enough MAC creates should iterate without
        # any given thread exhausting its retry count.
        for retry in xrange(CONF.QUARK.mac_address_retry_max):
            LOG.info("Attemping to find a range to create a new MAC in "
                     "(step 2 of 3), attempt {0} of {1}".format(
                         retry + 1, CONF.QUARK.mac_address_retry_max))
            next_address = None
            with context.session.begin():
                try:
                    fn = db_api.mac_address_range_find_allocation_counts
                    mac_range = \
                        fn(context, address=mac_address,
                           use_forbidden_mac_range=use_forbidden_mac_range)

                    if not mac_range:
                        LOG.info("No MAC ranges could be found given "
                                 "the criteria")
                        break

                    rng, addr_count = mac_range
                    LOG.info("Found a MAC range {0}".format(rng["cidr"]))

                    last = rng["last_address"]
                    first = rng["first_address"]
                    if (last - first + 1) <= addr_count:
                        # Somehow, the range got filled up without us
                        # knowing, so set the next_auto_assign to be -1
                        # so we never try to create new ones
                        # in this range
                        db_api.mac_range_update_set_full(context, rng)
                        LOG.info("MAC range {0} is full".format(rng["cidr"]))
                        continue

                    if mac_address:
                        next_address = mac_address
                    else:
                        next_address = rng["next_auto_assign_mac"]
                        if next_address + 1 > rng["last_address"]:
                            db_api.mac_range_update_set_full(context, rng)
                        else:
                            db_api.mac_range_update_next_auto_assign_mac(
                                context, rng)
                        context.session.refresh(rng)
                except Exception:
                    LOG.exception("Error in updating mac range")
                    continue

            # Based on the above, this should only fail if a MAC was
            # was explicitly chosen at some point. As such, fall through
            # here and get in line for a new MAC address to try
            try:
                mac_readable = str(netaddr.EUI(next_address))
                LOG.info("Attempting to create new MAC {0} "
                         "(step 3 of 3)".format(mac_readable))
                with context.session.begin():
                    address = db_api.mac_address_create(
                        context, address=next_address,
                        mac_address_range_id=rng["id"])
                    LOG.info("MAC assignment for port ID {0} completed with "
                             "address {1}".format(port_id, mac_readable))
                    return address
            except Exception:
                LOG.info("Failed to create new MAC {0}".format(mac_readable))
                LOG.exception("Error in creating mac. MAC possibly duplicate")
                continue

        raise n_exc_ext.MacAddressGenerationFailure(net_id=net_id)
Example #15
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")

            if subnet and subnet["ip_policy"]:
                ip_policy_cidrs = subnet["ip_policy"].get_cidrs_ip_set()
            else:
                ip_policy_cidrs = netaddr.IPSet([])

            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 ip_address_failure(net_id)

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

                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

                try:
                    with context.session.begin():
                        address = 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))
                        # alexm: need to notify from here because this code
                        # does not go through the _allocate_from_subnet() path.
                        notify(context, 'ip.add', address)
                        return address
                except db_exception.DBDuplicateEntry:
                    # This shouldn't ever happen, since we hold a unique MAC
                    # address from the previous IPAM step.
                    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 #16
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 #17
0
    def select_subnet(self, context, net_id, ip_address, segment_id,
                      subnet_ids=None, **filters):
        LOG.info("Selecting subnet(s) - (Step 2 of 3) [{0}]".format(
            utils.pretty_kwargs(network_id=net_id, ip_address=ip_address,
                                segment_id=segment_id, subnet_ids=subnet_ids,
                                ip_version=filters.get("ip_version"))))

        with context.session.begin():
            subnets = db_api.subnet_find_ordered_by_most_full(
                context, net_id, segment_id=segment_id, scope=db_api.ALL,
                subnet_id=subnet_ids, **filters)

            if not subnets:
                LOG.info("No subnets found given the search criteria!")

            for subnet, ips_in_subnet in subnets:
                ipnet = netaddr.IPNetwork(subnet["cidr"])
                LOG.info("Trying subnet ID: {0} - CIDR: {1}".format(
                    subnet["id"], subnet["_cidr"]))
                if ip_address:
                    requested_ip = netaddr.IPAddress(ip_address)
                    if ipnet.version == 4 and requested_ip.version != 4:
                        requested_ip = requested_ip.ipv4()
                    if requested_ip not in ipnet:
                        if subnet_ids is not None:
                            LOG.info("Requested IP {0} not in subnet {1}, "
                                     "retrying".format(str(requested_ip),
                                                       str(ipnet)))
                            raise q_exc.IPAddressNotInSubnet(
                                ip_addr=ip_address, subnet_id=subnet["id"])
                        continue

                ip_policy = None
                if not ip_address:
                    # Policies don't prevent explicit assignment, so we only
                    # need to check if we're allocating a new IP
                    ip_policy = subnet.get("ip_policy")

                policy_size = ip_policy["size"] if ip_policy else 0

                if ipnet.size > (ips_in_subnet + policy_size - 1):
                    if not ip_address and subnet["ip_version"] == 4:
                        ip = subnet["next_auto_assign_ip"]
                        # NOTE(mdietz): When atomically updated, this probably
                        #               doesn't need the lower bounds check but
                        #               I'm not comfortable removing it yet.
                        updated = 0
                        if ip < subnet["first_ip"] or ip > subnet["last_ip"]:
                            LOG.info("Marking subnet {0} as full".format(
                                subnet["id"]))
                            updated = db_api.subnet_update_set_full(context,
                                                                    subnet)
                        else:
                            auto_inc = db_api.subnet_update_next_auto_assign_ip
                            updated = auto_inc(context, subnet)

                        if updated:
                            context.session.refresh(subnet)
                        else:
                            # This means the subnet was marked full
                            # while we were checking out policies.
                            # Fall out and go back to the outer retry
                            # loop.
                            return

                    LOG.info("Subnet {0} - {1} {2} looks viable, "
                             "returning".format(subnet["id"], subnet["_cidr"],
                                                subnet["next_auto_assign_ip"]))
                    return subnet
                else:
                    LOG.info("Marking subnet {0} as full".format(subnet["id"]))
                    db_api.subnet_update_set_full(context, subnet)
Example #18
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 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.
            LOG.info("Identified as v6 case, deferring to IP create path")
            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:
                    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, "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(CONF.QUARK.ip_address_retry_max):
            LOG.info("Attempt {0} of {1}".format(
                retry + 1, 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 :-/
                        LOG.info("Potentially reallocatable IP found: "
                                 "{0}".format(address["address_readable"]))
                        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:
                                LOG.info("Deleting Address {0} due to policy "
                                         "violation".format(
                                             address["address_readable"]))

                                context.session.delete(address)
                                continue
                            if addr in cidr:
                                LOG.info("Marking Address {0} as "
                                         "allocated".format(
                                             address["address_readable"]))
                                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(),
                                    port_id=port_id,
                                    address_type=kwargs.get('address_type',
                                                            ip_types.FIXED))
                                return [updated_address]
                            else:
                                # Make sure we never find it again
                                LOG.info("Address {0} isn't in the subnet "
                                         "it claims to be in".format(
                                             address["address_readable"]))
                                context.session.delete(address)
                    else:
                        LOG.info("Couldn't find any reallocatable addresses "
                                 "given the criteria")
                        break
            except Exception:
                LOG.exception("Error in reallocate ip...")
        return []
Example #19
0
    def allocate_mac_address(self, context, net_id, port_id, reuse_after,
                             mac_address=None):
        if mac_address:
            mac_address = netaddr.EUI(mac_address).value

        kwargs = {"network_id": net_id, "port_id": port_id,
                  "mac_address": mac_address}
        LOG.info(("Attempting to allocate a new MAC address "
                  "[{0}]").format(utils.pretty_kwargs(**kwargs)))

        for retry in xrange(CONF.QUARK.mac_address_retry_max):
            LOG.info("Attemping to reallocate deallocated MAC (step 1 of 3),"
                     " attempt {0} of {1}".format(
                         retry + 1, CONF.QUARK.mac_address_retry_max))
            try:
                with context.session.begin():
                    deallocated_mac = db_api.mac_address_find(
                        context, lock_mode=True, reuse_after=reuse_after,
                        deallocated=True, scope=db_api.ONE,
                        address=mac_address, order_by="address ASC")

                    if deallocated_mac:
                        dealloc = netaddr.EUI(deallocated_mac["address"])
                        LOG.info("Found a suitable deallocated MAC {0}".format(
                            str(dealloc)))

                        address = db_api.mac_address_update(
                            context, deallocated_mac, deallocated=False,
                            deallocated_at=None)

                        LOG.info("MAC assignment for port ID {0} completed "
                                 "with address {1}".format(port_id, dealloc))
                        return address
                    break
            except Exception:
                LOG.exception("Error in mac reallocate...")
                continue

        LOG.info("Couldn't find a suitable deallocated MAC, attempting "
                 "to create a new one")

        # This could fail if a large chunk of MACs were chosen explicitly,
        # but under concurrent load enough MAC creates should iterate without
        # any given thread exhausting its retry count.
        for retry in xrange(CONF.QUARK.mac_address_retry_max):
            LOG.info("Attemping to find a range to create a new MAC in "
                     "(step 2 of 3), attempt {0} of {1}".format(
                         retry + 1, CONF.QUARK.mac_address_retry_max))
            next_address = None
            with context.session.begin():
                try:
                    fn = db_api.mac_address_range_find_allocation_counts
                    mac_range = fn(context, address=mac_address)

                    if not mac_range:
                        LOG.info("No MAC ranges could be found given "
                                 "the criteria")
                        break

                    rng, addr_count = mac_range
                    LOG.info("Found a MAC range {0}".format(rng["cidr"]))

                    last = rng["last_address"]
                    first = rng["first_address"]
                    if (last - first + 1) <= addr_count:
                        # Somehow, the range got filled up without us
                        # knowing, so set the next_auto_assign to be -1
                        # so we never try to create new ones
                        # in this range
                        rng["next_auto_assign_mac"] = -1
                        context.session.add(rng)
                        LOG.info("MAC range {0} is full".format(rng["cidr"]))
                        continue

                    if mac_address:
                        next_address = mac_address
                    else:
                        next_address = rng["next_auto_assign_mac"]
                        next_auto = next_address + 1
                        if next_auto > last:
                            next_auto = -1
                        db_api.mac_address_range_update(
                            context, rng, next_auto_assign_mac=next_auto)

                except Exception:
                    LOG.exception("Error in updating mac range")
                    continue

            # Based on the above, this should only fail if a MAC was
            # was explicitly chosen at some point. As such, fall through
            # here and get in line for a new MAC address to try
            try:
                mac_readable = str(netaddr.EUI(next_address))
                LOG.info("Attempting to create new MAC {0} "
                         "(step 3 of 3)".format(mac_readable))
                with context.session.begin():
                    address = db_api.mac_address_create(
                        context, address=next_address,
                        mac_address_range_id=rng["id"])
                    LOG.info("MAC assignment for port ID {0} completed with "
                             "address {1}".format(port_id, mac_readable))
                    return address
            except Exception:
                LOG.info("Failed to create new MAC {0}".format(mac_readable))
                LOG.exception("Error in creating mac. MAC possibly duplicate")
                continue

        raise exceptions.MacAddressGenerationFailure(net_id=net_id)
Example #20
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 #21
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 #22
0
    def allocate_mac_address(self, context, net_id, port_id, reuse_after,
                             mac_address=None,
                             use_forbidden_mac_range=False):
        if mac_address:
            mac_address = netaddr.EUI(mac_address).value

        kwargs = {"network_id": net_id, "port_id": port_id,
                  "mac_address": mac_address,
                  "use_forbidden_mac_range": use_forbidden_mac_range}
        LOG.info(("Attempting to allocate a new MAC address "
                  "[{0}]").format(utils.pretty_kwargs(**kwargs)))

        for retry in xrange(CONF.QUARK.mac_address_retry_max):
            LOG.info("Attemping to reallocate deallocated MAC (step 1 of 3),"
                     " attempt {0} of {1}".format(
                         retry + 1, CONF.QUARK.mac_address_retry_max))
            try:
                with context.session.begin():
                    transaction = db_api.transaction_create(context)
                update_kwargs = {
                    "deallocated": False,
                    "deallocated_at": None,
                    "transaction_id": transaction.id
                }
                filter_kwargs = {
                    "reuse_after": reuse_after,
                    "deallocated": True,
                    "address": mac_address
                }
                elevated = context.elevated()
                result = db_api.mac_address_reallocate(
                    elevated, update_kwargs, **filter_kwargs)
                if not result:
                    break

                reallocated_mac = db_api.mac_address_reallocate_find(
                    elevated, transaction.id)
                if reallocated_mac:
                    dealloc = netaddr.EUI(reallocated_mac["address"])
                    LOG.info("Found a suitable deallocated MAC {0}".format(
                        str(dealloc)))
                    LOG.info("MAC assignment for port ID {0} completed "
                             "with address {1}".format(port_id, dealloc))
                    return reallocated_mac
            except Exception:
                LOG.exception("Error in mac reallocate...")
                continue

        LOG.info("Couldn't find a suitable deallocated MAC, attempting "
                 "to create a new one")

        # This could fail if a large chunk of MACs were chosen explicitly,
        # but under concurrent load enough MAC creates should iterate without
        # any given thread exhausting its retry count.
        for retry in xrange(CONF.QUARK.mac_address_retry_max):
            LOG.info("Attemping to find a range to create a new MAC in "
                     "(step 2 of 3), attempt {0} of {1}".format(
                         retry + 1, CONF.QUARK.mac_address_retry_max))
            next_address = None
            with context.session.begin():
                try:
                    fn = db_api.mac_address_range_find_allocation_counts
                    mac_range = \
                        fn(context, address=mac_address,
                           use_forbidden_mac_range=use_forbidden_mac_range)

                    if not mac_range:
                        LOG.info("No MAC ranges could be found given "
                                 "the criteria")
                        break

                    rng, addr_count = mac_range
                    LOG.info("Found a MAC range {0}".format(rng["cidr"]))

                    last = rng["last_address"]
                    first = rng["first_address"]
                    if (last - first + 1) <= addr_count:
                        # Somehow, the range got filled up without us
                        # knowing, so set the next_auto_assign to be -1
                        # so we never try to create new ones
                        # in this range
                        db_api.mac_range_update_set_full(context, rng)
                        LOG.info("MAC range {0} is full".format(rng["cidr"]))
                        continue

                    if mac_address:
                        next_address = mac_address
                    else:
                        next_address = rng["next_auto_assign_mac"]
                        if next_address + 1 > rng["last_address"]:
                            db_api.mac_range_update_set_full(context, rng)
                        else:
                            db_api.mac_range_update_next_auto_assign_mac(
                                context, rng)
                        context.session.refresh(rng)
                except Exception:
                    LOG.exception("Error in updating mac range")
                    continue

            # Based on the above, this should only fail if a MAC was
            # was explicitly chosen at some point. As such, fall through
            # here and get in line for a new MAC address to try
            try:
                mac_readable = str(netaddr.EUI(next_address))
                LOG.info("Attempting to create new MAC {0} "
                         "(step 3 of 3)".format(mac_readable))
                with context.session.begin():
                    address = db_api.mac_address_create(
                        context, address=next_address,
                        mac_address_range_id=rng["id"])
                    LOG.info("MAC assignment for port ID {0} completed with "
                             "address {1}".format(port_id, mac_readable))
                    return address
            except Exception:
                LOG.info("Failed to create new MAC {0}".format(mac_readable))
                LOG.exception("Error in creating mac. MAC possibly duplicate")
                continue

        raise exceptions.MacAddressGenerationFailure(net_id=net_id)