def _create_in_db(self, context, updates):
        parent_id = None
        root_id = None
        # User supplied a parent, let's make sure it exists
        parent_uuid = updates.pop('parent_provider_uuid')
        if parent_uuid is not None:
            # Setting parent to ourselves doesn't make any sense
            if parent_uuid == self.uuid:
                raise exception.ObjectActionError(
                    action='create',
                    reason='parent provider UUID cannot be same as UUID. '
                           'Please set parent provider UUID to None if '
                           'there is no parent.')

            parent_ids = res_ctx.provider_ids_from_uuid(context, parent_uuid)
            if parent_ids is None:
                raise exception.ObjectActionError(
                    action='create',
                    reason='parent provider UUID does not exist.')

            parent_id = parent_ids.id
            root_id = parent_ids.root_id
            updates['root_provider_id'] = root_id
            updates['parent_provider_id'] = parent_id
            self.root_provider_uuid = parent_ids.root_uuid

        db_rp = models.ResourceProvider()
        db_rp.update(updates)
        context.session.add(db_rp)
        context.session.flush()

        self.id = db_rp.id
        self.generation = db_rp.generation

        if root_id is None:
            # User did not specify a parent when creating this provider, so the
            # root_provider_id needs to be set to this provider's newly-created
            # internal ID
            db_rp.root_provider_id = db_rp.id
            context.session.add(db_rp)
            context.session.flush()
            self.root_provider_uuid = self.uuid
def _get_all_by_filters_from_db(context, filters):
    # Eg. filters can be:
    #  filters = {
    #      'name': <name>,
    #      'uuid': <uuid>,
    #      'member_of': [[<aggregate_uuid>, <aggregate_uuid>],
    #                    [<aggregate_uuid>]]
    #      'forbidden_aggs': [<aggregate_uuid>, <aggregate_uuid>]
    #      'resources': {
    #          'VCPU': 1,
    #          'MEMORY_MB': 1024
    #      },
    #      'in_tree': <uuid>,
    #      'required': [<trait_name>, ...]
    #  }
    if not filters:
        filters = {}
    else:
        # Since we modify the filters, copy them so that we don't modify
        # them in the calling program.
        filters = copy.deepcopy(filters)
    name = filters.pop('name', None)
    uuid = filters.pop('uuid', None)
    member_of = filters.pop('member_of', [])
    forbidden_aggs = filters.pop('forbidden_aggs', [])
    required = set(filters.pop('required', []))
    forbidden = set([trait for trait in required
                     if trait.startswith('!')])
    required = required - forbidden
    forbidden = set([trait.lstrip('!') for trait in forbidden])
    resources = filters.pop('resources', {})
    in_tree = filters.pop('in_tree', None)

    rp = sa.alias(_RP_TBL, name="rp")
    root_rp = sa.alias(_RP_TBL, name="root_rp")
    parent_rp = sa.alias(_RP_TBL, name="parent_rp")

    cols = [
        rp.c.id,
        rp.c.uuid,
        rp.c.name,
        rp.c.generation,
        rp.c.updated_at,
        rp.c.created_at,
        root_rp.c.uuid.label("root_provider_uuid"),
        parent_rp.c.uuid.label("parent_provider_uuid"),
    ]

    rp_to_root = sa.join(
        rp, root_rp,
        rp.c.root_provider_id == root_rp.c.id)
    rp_to_parent = sa.outerjoin(
        rp_to_root, parent_rp,
        rp.c.parent_provider_id == parent_rp.c.id)

    query = sa.select(cols).select_from(rp_to_parent)

    if name:
        query = query.where(rp.c.name == name)
    if uuid:
        query = query.where(rp.c.uuid == uuid)
    if in_tree:
        # The 'in_tree' parameter is the UUID of a resource provider that
        # the caller wants to limit the returned providers to only those
        # within its "provider tree". So, we look up the resource provider
        # having the UUID specified by the 'in_tree' parameter and grab the
        # root_provider_id value of that record. We can then ask for only
        # those resource providers having a root_provider_id of that value.
        tree_ids = res_ctx.provider_ids_from_uuid(context, in_tree)
        if tree_ids is None:
            # List operations should simply return an empty list when a
            # non-existing resource provider UUID is given.
            return []
        root_id = tree_ids.root_id
        query = query.where(rp.c.root_provider_id == root_id)
    if required:
        trait_map = trait_obj.ids_from_names(context, required)
        trait_rps = res_ctx._get_provider_ids_having_all_traits(
            context, trait_map)
        if not trait_rps:
            return []
        query = query.where(rp.c.id.in_(trait_rps))
    if forbidden:
        trait_map = trait_obj.ids_from_names(context, forbidden)
        trait_rps = res_ctx.get_provider_ids_having_any_trait(
            context, trait_map)
        if trait_rps:
            query = query.where(~rp.c.id.in_(trait_rps))
    if member_of:
        rps_in_aggs = res_ctx.provider_ids_matching_aggregates(
            context, member_of)
        if not rps_in_aggs:
            return []
        query = query.where(rp.c.id.in_(rps_in_aggs))
    if forbidden_aggs:
        rps_bad_aggs = res_ctx.provider_ids_matching_aggregates(
            context, [forbidden_aggs])
        if rps_bad_aggs:
            query = query.where(~rp.c.id.in_(rps_bad_aggs))
    for rc_name, amount in resources.items():
        rc_id = context.rc_cache.id_from_string(rc_name)
        rps_with_resource = res_ctx.get_providers_with_resource(
            context, rc_id, amount)
        rps_with_resource = (rp[0] for rp in rps_with_resource)
        query = query.where(rp.c.id.in_(rps_with_resource))

    return context.session.execute(query).fetchall()
    def _update_in_db(self, context, id, updates, allow_reparenting):
        # A list of resource providers in the subtree of resource provider to
        # update
        subtree_rps = []
        # The new root RP if changed
        new_root_id = None
        new_root_uuid = None
        if 'parent_provider_uuid' in updates:
            my_ids = res_ctx.provider_ids_from_uuid(context, self.uuid)
            parent_uuid = updates.pop('parent_provider_uuid')
            if parent_uuid is not None:
                parent_ids = res_ctx.provider_ids_from_uuid(
                    context, parent_uuid)
                # User supplied a parent, let's make sure it exists
                if parent_ids is None:
                    raise exception.ObjectActionError(
                        action='create',
                        reason='parent provider UUID does not exist.')
                if (my_ids.parent_id is not None and
                        my_ids.parent_id != parent_ids.id and
                        not allow_reparenting):
                    raise exception.ObjectActionError(
                        action='update',
                        reason='re-parenting a provider is not currently '
                               'allowed.')
                # So the user specified a new parent. We have to make sure
                # that the new parent is not a descendant of the
                # current RP to avoid a loop in the graph. It could be
                # easily checked by traversing the tree from the new parent
                # up to the root and see if we ever hit the current RP
                # along the way. However later we need to update every
                # descendant of the current RP with a possibly new root
                # so we go with the more expensive way and gather every
                # descendant for the current RP and check if the new
                # parent is part of that set.
                subtree_rps = self.get_subtree(context)
                subtree_rp_uuids = {rp.uuid for rp in subtree_rps}
                if parent_uuid in subtree_rp_uuids:
                    raise exception.ObjectActionError(
                        action='update',
                        reason='creating loop in the provider tree is '
                               'not allowed.')

                updates['root_provider_id'] = parent_ids.root_id
                updates['parent_provider_id'] = parent_ids.id
                self.root_provider_uuid = parent_ids.root_uuid
                new_root_id = parent_ids.root_id
                new_root_uuid = parent_ids.root_uuid
            else:
                if my_ids.parent_id is not None:
                    if not allow_reparenting:
                        raise exception.ObjectActionError(
                            action='update',
                            reason='un-parenting a provider is not currently '
                                   'allowed.')

                    # we don't need to do loop detection but we still need to
                    # collect the RPs from the subtree so that the new root
                    # value is updated in the whole subtree below.
                    subtree_rps = self.get_subtree(context)

                    # this RP becomes a new root RP
                    updates['root_provider_id'] = my_ids.id
                    updates['parent_provider_id'] = None
                    self.root_provider_uuid = my_ids.uuid
                    new_root_id = my_ids.id
                    new_root_uuid = my_ids.uuid

        db_rp = context.session.query(models.ResourceProvider).filter_by(
            id=id).first()
        db_rp.update(updates)
        context.session.add(db_rp)

        # We should also update the root providers of the resource providers
        # that are in our subtree
        for rp in subtree_rps:
            # If the parent is not updated, this clause is skipped since the
            # `subtree_rps` has no element.
            rp.root_provider_uuid = new_root_uuid
            db_rp = context.session.query(
                models.ResourceProvider).filter_by(id=rp.id).first()
            data = {'root_provider_id': new_root_id}
            db_rp.update(data)
            context.session.add(db_rp)

        try:
            context.session.flush()
        except sqla_exc.IntegrityError:
            # NOTE(jaypipes): Another thread snuck in and deleted the parent
            # for this resource provider in between the above check for a valid
            # parent provider and here...
            raise exception.ObjectActionError(
                action='update',
                reason='parent provider UUID does not exist.')
Example #4
0
    def _update_in_db(self, context, id, updates):
        # A list of resource providers in the same tree with the
        # resource provider to update
        same_tree = []
        if 'parent_provider_uuid' in updates:
            # TODO(jaypipes): For now, "re-parenting" and "un-parenting" are
            # not possible. If the provider already had a parent, we don't
            # allow changing that parent due to various issues, including:
            #
            # * if the new parent is a descendant of this resource provider, we
            #   introduce the possibility of a loop in the graph, which would
            #   be very bad
            # * potentially orphaning heretofore-descendants
            #
            # So, for now, let's just prevent re-parenting...
            my_ids = res_ctx.provider_ids_from_uuid(context, self.uuid)
            parent_uuid = updates.pop('parent_provider_uuid')
            if parent_uuid is not None:
                parent_ids = res_ctx.provider_ids_from_uuid(
                    context, parent_uuid)
                # User supplied a parent, let's make sure it exists
                if parent_ids is None:
                    raise exception.ObjectActionError(
                        action='create',
                        reason='parent provider UUID does not exist.')
                if (my_ids.parent_id is not None
                        and my_ids.parent_id != parent_ids.id):
                    raise exception.ObjectActionError(
                        action='update',
                        reason='re-parenting a provider is not currently '
                        'allowed.')
                if my_ids.parent_uuid is None:
                    # So the user specifies a parent for an RP that doesn't
                    # have one. We have to check that by this new parent we
                    # don't create a loop in the tree. Basically the new parent
                    # cannot be the RP itself or one of its descendants.
                    # However as the RP's current parent is None the above
                    # condition is the same as "the new parent cannot be any RP
                    # from the current RP tree".
                    same_tree = get_all_by_filters(
                        context, filters={'in_tree': self.uuid})
                    rp_uuids_in_the_same_tree = [rp.uuid for rp in same_tree]
                    if parent_uuid in rp_uuids_in_the_same_tree:
                        raise exception.ObjectActionError(
                            action='update',
                            reason='creating loop in the provider tree is '
                            'not allowed.')

                updates['root_provider_id'] = parent_ids.root_id
                updates['parent_provider_id'] = parent_ids.id
                self.root_provider_uuid = parent_ids.root_uuid
            else:
                if my_ids.parent_id is not None:
                    raise exception.ObjectActionError(
                        action='update',
                        reason='un-parenting a provider is not currently '
                        'allowed.')

        db_rp = context.session.query(
            models.ResourceProvider).filter_by(id=id).first()
        db_rp.update(updates)
        context.session.add(db_rp)

        # We should also update the root providers of resource providers
        # originally in the same tree. If re-parenting is supported,
        # this logic should be changed to update only descendents of the
        # re-parented resource providers, not all the providers in the tree.
        for rp in same_tree:
            # If the parent is not updated, this clause is skipped since the
            # `same_tree` has no element.
            rp.root_provider_uuid = parent_ids.root_uuid
            db_rp = context.session.query(
                models.ResourceProvider).filter_by(id=rp.id).first()
            data = {'root_provider_id': parent_ids.root_id}
            db_rp.update(data)
            context.session.add(db_rp)

        try:
            context.session.flush()
        except sqla_exc.IntegrityError:
            # NOTE(jaypipes): Another thread snuck in and deleted the parent
            # for this resource provider in between the above check for a valid
            # parent provider and here...
            raise exception.ObjectActionError(
                action='update', reason='parent provider UUID does not exist.')