b09f2f917f
The Trait and ResourceClass db objects have the same essential structure and are used throughout the code in similar ways: * turn a name into an id * turn an id into a name * get an unfiltered list of names * make a mapping of ids to names In I409a5e819a72d64e66ee390e4528da0c503d8d05 we made the resource class cache request specific. Doing that work made it pretty clear we could have a similar cache for traits and as a result visit the traits db fewer times per request. The implementation is straightforward: make an _AttributeCache super class that is a parent to ResourceClassCache. Create TraitCache as a sibling. The sole difference is the table used for the data authority and the exception raised when an attribute is not found. The new super class has been refactored to use private attributes and methods. A 'get_all' method is added to the cache to list the full collection of dicts it contains. That can be be directly transformed into Trait and ResourceClass objects. The order of the results of this method are not predictable, and sorting them would add cost for no benefit, so gabbi tests which had previously relied on the ordered of returned resource classes have been removed. From the API, listing traits and resource classes (without filters) now uses the cache instead of going to the db. Where filters (in traits) are required, the db is accessed. The research_context turns lists of trait names into id, name maps for required and forbidden traits. Further, anywhere the traits table was joined to create a name of an id, the cache is used instead. This allows to drop some joins and operate fully in-process and in-RAM. No additional loops are added to make this happen: the translation is done in existing loops. The upshot of these changes is that unless there is a write operation on a trait or resource class, both tables are scanned at most once in any request. And when they are scanned it is to list their entire contents. As noted in the _AttributeCache docstring there are restrictions on what kinds of entities can use the cache and some necessary precautions. Change-Id: Ia19ea2b4ecdde25323579edf60ad6269d05e75a2
60 lines
2.5 KiB
Python
60 lines
2.5 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from oslo_context import context
|
|
from oslo_db.sqlalchemy import enginefacade
|
|
|
|
from placement import attribute_cache
|
|
from placement import exception
|
|
from placement import policy
|
|
|
|
|
|
@enginefacade.transaction_context_provider
|
|
class RequestContext(context.RequestContext):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.config = kwargs.pop('config', None)
|
|
self.rc_cache = attribute_cache.ResourceClassCache(self)
|
|
self.trait_cache = attribute_cache.TraitCache(self)
|
|
super(RequestContext, self).__init__(*args, **kwargs)
|
|
|
|
def can(self, action, target=None, fatal=True):
|
|
"""Verifies that the given action is valid on the target in this
|
|
context.
|
|
|
|
:param action: string representing the action to be checked.
|
|
:param target: As much information about the object being operated on
|
|
as possible. The target argument should be a dict instance or an
|
|
instance of a class that fully supports the Mapping abstract base
|
|
class and deep copying. For object creation this should be a
|
|
dictionary representing the location of the object e.g.
|
|
``{'project_id': context.project_id}``. If None, then this default
|
|
target will be considered::
|
|
|
|
{'project_id': self.project_id, 'user_id': self.user_id}
|
|
:param fatal: if False, will return False when an
|
|
exception.PolicyNotAuthorized occurs.
|
|
:raises placement.exception.PolicyNotAuthorized:
|
|
if verification fails and fatal is True.
|
|
:return: returns a non-False value (not necessarily "True") if
|
|
authorized and False if not authorized and fatal is False.
|
|
"""
|
|
if target is None:
|
|
target = {'project_id': self.project_id,
|
|
'user_id': self.user_id}
|
|
try:
|
|
return policy.authorize(self, action, target)
|
|
except exception.PolicyNotAuthorized:
|
|
if fatal:
|
|
raise
|
|
return False
|