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.')
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.')