예제 #1
0
def _check_capacity_exceeded(context, allocs):
    """Checks to see if the supplied allocation records would result in any of
    the inventories involved having their capacity exceeded.

    Raises an InvalidAllocationCapacityExceeded exception if any inventory
    would be exhausted by the allocation. Raises an
    InvalidAllocationConstraintsViolated exception if any of the `step_size`,
    `min_unit` or `max_unit` constraints in an inventory will be violated
    by any one of the allocations.

    If no inventories would be exceeded or violated by the allocations, the
    function returns a list of `ResourceProvider` objects that contain the
    generation at the time of the check.

    :param ctx: `placement.context.RequestContext` that has an oslo_db
                Session
    :param allocs: List of `Allocation` objects to check
    """
    rc_names = set([a.resource_class for a in allocs])
    # Make sure that all the rc_names are valid
    query = """
            MATCH (rc:RESOURCE_CLASS)
            WHERE rc.name IN {names}
            RETURN rc.name AS rc_name
    """
    result = context.tx.run(query, names=list(rc_names))
    db_names = set([rec["rc_name"] for rec in result])
    set_bad_names = rc_names - db_names
    if set_bad_names:
        bad_names = ",".join(set_bad_names)
        raise exception.ResourceClassNotFound(resource_class=bad_names)

    provider_uuids = set([a.resource_provider.uuid for a in allocs])
    query = """
            MATCH (rp:RESOURCE_PROVIDER)-[:PROVIDES]->(rc)
            WHERE rp.uuid IN {rp_uuids}
            AND labels(rc)[0] IN {labels}
            WITH rp, rc
            OPTIONAL MATCH p=(cs:CONSUMER)-[:USES]->(rc)
            WITH rp, rc, relationships(p)[0] AS allocs
            WITH rp, rc, sum(allocs.amount) AS total_usages
            RETURN rp, rc, labels(rc)[0] AS rc_name, total_usages
    """
    result = context.tx.run(query,
                            rp_uuids=list(provider_uuids),
                            labels=list(rc_names)).data()

    # Create a map keyed by (rp_uuid, res_class) for the records in the DB
    usage_map = {}
    provs_with_inv = set()
    for record in result:
        map_key = (record["rp"]["uuid"], record["rc_name"])
        if map_key in usage_map:
            raise KeyError("%s already in usage_map, bad query" % str(map_key))
        usage_map[map_key] = record
        provs_with_inv.add(record["rp"]["uuid"])
    # Ensure that all providers have existing inventory
    missing_provs = provider_uuids - provs_with_inv
    if missing_provs:
        class_str = ", ".join(rc_names)
        provider_str = ", ".join(missing_provs)
        raise exception.InvalidInventory(resource_class=class_str,
                                         resource_provider=provider_str)

    res_providers = {}
    rp_resource_class_sum = collections.defaultdict(
        lambda: collections.defaultdict(int))
    for alloc in allocs:
        rc_name = alloc.resource_class
        rp_uuid = alloc.resource_provider.uuid
        if rp_uuid not in res_providers:
            res_providers[rp_uuid] = alloc.resource_provider
        amount_needed = alloc.used
        # No use checking usage if we're not asking for anything
        if amount_needed == 0:
            continue
        rp_resource_class_sum[rp_uuid][rc_name] += amount_needed
        key = (rp_uuid, rc_name)
        try:
            usage_record = usage_map[key]
        except KeyError:
            # The resource class at rc_name is not in the usage map.
            raise exception.InvalidInventory(
                resource_class=alloc.resource_class, resource_provider=rp_uuid)
        rc_record = usage_record["rc"]
        allocation_ratio = rc_record["allocation_ratio"]
        min_unit = rc_record["min_unit"]
        max_unit = rc_record["max_unit"]
        step_size = rc_record["step_size"]
        total = rc_record["total"]
        reserved = rc_record["reserved"]

        # check min_unit, max_unit, step_size
        if ((amount_needed < min_unit) or (amount_needed > max_unit)
                or (amount_needed % step_size)):
            LOG.warning(
                "Allocation for %(rc)s on resource provider %(rp)s "
                "violates min_unit, max_unit, or step_size. "
                "Requested: %(requested)s, min_unit: %(min_unit)s, "
                "max_unit: %(max_unit)s, step_size: %(step_size)s", {
                    "rc": alloc.resource_class,
                    "rp": rp_uuid,
                    "requested": amount_needed,
                    "min_unit": min_unit,
                    "max_unit": max_unit,
                    "step_size": step_size
                })
            raise exception.InvalidAllocationConstraintsViolated(
                resource_class=alloc.resource_class, resource_provider=rp_uuid)

        # Should never be null, but just in case...
        used = usage_record["total_usages"] or 0
        capacity = (total - reserved) * allocation_ratio
        if (capacity < (used + amount_needed) or capacity <
            (used + rp_resource_class_sum[rp_uuid][rc_name])):
            LOG.warning(
                "Over capacity for %(rc)s on resource provider %(rp)s. "
                "Needed: %(needed)s, Used: %(used)s, Capacity: %(cap)s", {
                    "rc": alloc.resource_class,
                    "rp": rp_uuid,
                    "needed": amount_needed,
                    "used": used,
                    "cap": capacity
                })
            raise exception.InvalidAllocationCapacityExceeded(
                resource_class=alloc.resource_class, resource_provider=rp_uuid)
    return res_providers
예제 #2
0
def _check_capacity_exceeded(ctx, allocs):
    """Checks to see if the supplied allocation records would result in any of
    the inventories involved having their capacity exceeded.

    Raises an InvalidAllocationCapacityExceeded exception if any inventory
    would be exhausted by the allocation. Raises an
    InvalidAllocationConstraintsViolated exception if any of the `step_size`,
    `min_unit` or `max_unit` constraints in an inventory will be violated
    by any one of the allocations.

    If no inventories would be exceeded or violated by the allocations, the
    function returns a list of `ResourceProvider` objects that contain the
    generation at the time of the check.

    :param ctx: `placement.context.RequestContext` that has an oslo_db
                Session
    :param allocs: List of `Allocation` objects to check
    """
    # The SQL generated below looks like this:
    # SELECT
    #   rp.id,
    #   rp.uuid,
    #   rp.generation,
    #   inv.resource_class_id,
    #   inv.total,
    #   inv.reserved,
    #   inv.allocation_ratio,
    #   allocs.used
    # FROM resource_providers AS rp
    # JOIN inventories AS i1
    # ON rp.id = i1.resource_provider_id
    # LEFT JOIN (
    #    SELECT resource_provider_id, resource_class_id, SUM(used) AS used
    #    FROM allocations
    #    WHERE resource_class_id IN ($RESOURCE_CLASSES)
    #    AND resource_provider_id IN ($RESOURCE_PROVIDERS)
    #    GROUP BY resource_provider_id, resource_class_id
    # ) AS allocs
    # ON inv.resource_provider_id = allocs.resource_provider_id
    # AND inv.resource_class_id = allocs.resource_class_id
    # WHERE rp.id IN ($RESOURCE_PROVIDERS)
    # AND inv.resource_class_id IN ($RESOURCE_CLASSES)
    #
    # We then take the results of the above and determine if any of the
    # inventory will have its capacity exceeded.
    rc_ids = set(
        [rc_cache.RC_CACHE.id_from_string(a.resource_class) for a in allocs])
    provider_uuids = set([a.resource_provider.uuid for a in allocs])
    provider_ids = set([a.resource_provider.id for a in allocs])
    usage = sa.select([
        _ALLOC_TBL.c.resource_provider_id, _ALLOC_TBL.c.resource_class_id,
        sql.func.sum(_ALLOC_TBL.c.used).label('used')
    ])
    usage = usage.where(
        sa.and_(_ALLOC_TBL.c.resource_class_id.in_(rc_ids),
                _ALLOC_TBL.c.resource_provider_id.in_(provider_ids)))
    usage = usage.group_by(_ALLOC_TBL.c.resource_provider_id,
                           _ALLOC_TBL.c.resource_class_id)
    usage = sa.alias(usage, name='usage')

    inv_join = sql.join(
        _RP_TBL, _INV_TBL,
        sql.and_(_RP_TBL.c.id == _INV_TBL.c.resource_provider_id,
                 _INV_TBL.c.resource_class_id.in_(rc_ids)))
    primary_join = sql.outerjoin(
        inv_join, usage,
        sql.and_(
            _INV_TBL.c.resource_provider_id == usage.c.resource_provider_id,
            _INV_TBL.c.resource_class_id == usage.c.resource_class_id))
    cols_in_output = [
        _RP_TBL.c.id.label('resource_provider_id'),
        _RP_TBL.c.uuid,
        _RP_TBL.c.generation,
        _INV_TBL.c.resource_class_id,
        _INV_TBL.c.total,
        _INV_TBL.c.reserved,
        _INV_TBL.c.allocation_ratio,
        _INV_TBL.c.min_unit,
        _INV_TBL.c.max_unit,
        _INV_TBL.c.step_size,
        usage.c.used,
    ]

    sel = sa.select(cols_in_output).select_from(primary_join)
    sel = sel.where(
        sa.and_(_RP_TBL.c.id.in_(provider_ids),
                _INV_TBL.c.resource_class_id.in_(rc_ids)))
    records = ctx.session.execute(sel)
    # Create a map keyed by (rp_uuid, res_class) for the records in the DB
    usage_map = {}
    provs_with_inv = set()
    for record in records:
        map_key = (record['uuid'], record['resource_class_id'])
        if map_key in usage_map:
            raise KeyError("%s already in usage_map, bad query" % str(map_key))
        usage_map[map_key] = record
        provs_with_inv.add(record["uuid"])
    # Ensure that all providers have existing inventory
    missing_provs = provider_uuids - provs_with_inv
    if missing_provs:
        class_str = ', '.join(
            [rc_cache.RC_CACHE.string_from_id(rc_id) for rc_id in rc_ids])
        provider_str = ', '.join(missing_provs)
        raise exception.InvalidInventory(resource_class=class_str,
                                         resource_provider=provider_str)

    res_providers = {}
    rp_resource_class_sum = collections.defaultdict(
        lambda: collections.defaultdict(int))
    for alloc in allocs:
        rc_id = rc_cache.RC_CACHE.id_from_string(alloc.resource_class)
        rp_uuid = alloc.resource_provider.uuid
        if rp_uuid not in res_providers:
            res_providers[rp_uuid] = alloc.resource_provider
        amount_needed = alloc.used
        rp_resource_class_sum[rp_uuid][rc_id] += amount_needed
        # No use checking usage if we're not asking for anything
        if amount_needed == 0:
            continue
        key = (rp_uuid, rc_id)
        try:
            usage = usage_map[key]
        except KeyError:
            # The resource class at rc_id is not in the usage map.
            raise exception.InvalidInventory(
                resource_class=alloc.resource_class, resource_provider=rp_uuid)
        allocation_ratio = usage['allocation_ratio']
        min_unit = usage['min_unit']
        max_unit = usage['max_unit']
        step_size = usage['step_size']

        # check min_unit, max_unit, step_size
        if (amount_needed < min_unit or amount_needed > max_unit
                or amount_needed % step_size != 0):
            LOG.warning(
                "Allocation for %(rc)s on resource provider %(rp)s "
                "violates min_unit, max_unit, or step_size. "
                "Requested: %(requested)s, min_unit: %(min_unit)s, "
                "max_unit: %(max_unit)s, step_size: %(step_size)s", {
                    'rc': alloc.resource_class,
                    'rp': rp_uuid,
                    'requested': amount_needed,
                    'min_unit': min_unit,
                    'max_unit': max_unit,
                    'step_size': step_size
                })
            raise exception.InvalidAllocationConstraintsViolated(
                resource_class=alloc.resource_class, resource_provider=rp_uuid)

        # usage["used"] can be returned as None
        used = usage['used'] or 0
        capacity = (usage['total'] - usage['reserved']) * allocation_ratio
        if (capacity < (used + amount_needed) or capacity <
            (used + rp_resource_class_sum[rp_uuid][rc_id])):
            LOG.warning(
                "Over capacity for %(rc)s on resource provider %(rp)s. "
                "Needed: %(needed)s, Used: %(used)s, Capacity: %(cap)s", {
                    'rc': alloc.resource_class,
                    'rp': rp_uuid,
                    'needed': amount_needed,
                    'used': used,
                    'cap': capacity
                })
            raise exception.InvalidAllocationCapacityExceeded(
                resource_class=alloc.resource_class, resource_provider=rp_uuid)
    return res_providers