"Fossies" - the Fresh Open Source Software Archive

Member "openstack-cyborg-9.0.0/cyborg/db/sqlalchemy/api.py" (5 Oct 2022, 44090 Bytes) of package /linux/misc/openstack/openstack-cyborg-9.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "api.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 8.0.0_vs_9.0.0.

    1 # Copyright 2017 Huawei Technologies Co.,LTD.
    2 # All Rights Reserved.
    3 #
    4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 #    not use this file except in compliance with the License. You may obtain
    6 #    a copy of the License at
    7 #
    8 #         http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 #    Unless required by applicable law or agreed to in writing, software
   11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 #    License for the specific language governing permissions and limitations
   14 #    under the License.
   15 
   16 """SQLAlchemy storage backend."""
   17 
   18 import copy
   19 import threading
   20 import uuid
   21 
   22 from oslo_db import api as oslo_db_api
   23 from oslo_db import exception as db_exc
   24 from oslo_db.sqlalchemy import enginefacade
   25 from oslo_db.sqlalchemy import utils as sqlalchemyutils
   26 from oslo_log import log
   27 from oslo_utils import strutils
   28 from oslo_utils import timeutils
   29 from oslo_utils import uuidutils
   30 from sqlalchemy.orm.exc import NoResultFound
   31 from sqlalchemy.orm import load_only
   32 
   33 from cyborg.common import exception
   34 from cyborg.common.i18n import _
   35 from cyborg.db import api
   36 from cyborg.db.sqlalchemy import models
   37 
   38 _CONTEXT = threading.local()
   39 LOG = log.getLogger(__name__)
   40 
   41 main_context_manager = enginefacade.transaction_context()
   42 
   43 
   44 def get_backend():
   45     """The backend is this module itself."""
   46     return Connection()
   47 
   48 
   49 def _session_for_read():
   50     return enginefacade.reader.using(_CONTEXT)
   51 
   52 
   53 def _session_for_write():
   54     return enginefacade.writer.using(_CONTEXT)
   55 
   56 
   57 def get_session(use_slave=False, **kwargs):
   58     return main_context_manager._factory.get_legacy_facade().get_session(
   59         use_slave=use_slave, **kwargs)
   60 
   61 
   62 def model_query(context, model, *args, **kwargs):
   63     """Query helper for simpler session usage.
   64 
   65     :param context: Context of the query
   66     :param model: Model to query. Must be a subclass of ModelBase.
   67     :param args: Arguments to query. If None - model is used.
   68 
   69     Keyword arguments:
   70 
   71     :keyword project_only:
   72       If set to True, then will do query filter with context's project_id.
   73       if set to False or absent, then will not do query filter with context's
   74       project_id.
   75     :type project_only: bool
   76     """
   77 
   78     if kwargs.pop("project_only", False):
   79         kwargs["project_id"] = context.project_id
   80 
   81     with _session_for_read() as session:
   82         query = sqlalchemyutils.model_query(
   83             model, session, args, **kwargs)
   84         return query
   85 
   86 
   87 def add_identity_filter(query, value):
   88     """Adds an identity filter to a query.
   89 
   90     Filters results by ID, if supplied value is a valid integer.
   91     Otherwise attempts to filter results by UUID.
   92 
   93     :param query: Initial query to add filter to.
   94     :param value: Value for filtering results by.
   95     :return: Modified query.
   96     """
   97     if strutils.is_int_like(value):
   98         return query.filter_by(id=value)
   99     elif uuidutils.is_uuid_like(value):
  100         return query.filter_by(uuid=value)
  101     else:
  102         raise exception.InvalidIdentity(identity=value)
  103 
  104 
  105 def _paginate_query(context, model, query, limit=None, marker=None,
  106                     sort_key=None, sort_dir=None):
  107     sort_keys = ['id']
  108     if sort_key and sort_key not in sort_keys:
  109         sort_keys.insert(0, sort_key)
  110     try:
  111         query = sqlalchemyutils.paginate_query(query, model, limit, sort_keys,
  112                                                marker=marker,
  113                                                sort_dir=sort_dir)
  114     except db_exc.InvalidSortKey:
  115         raise exception.InvalidParameterValue(
  116             _('The sort_key value "%(key)s" is an invalid field for sorting')
  117             % {'key': sort_key})
  118     return query.all()
  119 
  120 
  121 class Connection(api.Connection):
  122     """SqlAlchemy connection."""
  123 
  124     def __init__(self):
  125         pass
  126 
  127     def attach_handle_create(self, context, values):
  128         if not values.get('uuid'):
  129             values['uuid'] = uuidutils.generate_uuid()
  130 
  131         attach_handle = models.AttachHandle()
  132         attach_handle.update(values)
  133 
  134         with _session_for_write() as session:
  135             try:
  136                 session.add(attach_handle)
  137                 session.flush()
  138             except db_exc.DBDuplicateEntry:
  139                 raise exception.AttachHandleAlreadyExists(uuid=values['uuid'])
  140             return attach_handle
  141 
  142     def attach_handle_get_by_uuid(self, context, uuid):
  143         query = model_query(
  144             context,
  145             models.AttachHandle).filter_by(uuid=uuid)
  146         try:
  147             return query.one()
  148         except NoResultFound:
  149             raise exception.ResourceNotFound(
  150                 resource='AttachHandle',
  151                 msg='with uuid=%s' % uuid)
  152 
  153     def attach_handle_get_by_id(self, context, id):
  154         query = model_query(
  155             context,
  156             models.AttachHandle).filter_by(id=id)
  157         try:
  158             return query.one()
  159         except NoResultFound:
  160             raise exception.ResourceNotFound(
  161                 resource='AttachHandle',
  162                 msg='with id=%s' % id)
  163 
  164     def attach_handle_list_by_type(self, context, attach_type='PCI'):
  165         query = model_query(context, models.AttachHandle). \
  166             filter_by(attach_type=attach_type)
  167         try:
  168             return query.all()
  169         except NoResultFound:
  170             raise exception.ResourceNotFound(
  171                 resource='AttachHandle',
  172                 msg='with type=%s' % attach_type)
  173 
  174     def attach_handle_get_by_filters(self, context,
  175                                      filters, sort_key='created_at',
  176                                      sort_dir='desc', limit=None,
  177                                      marker=None, join_columns=None):
  178         """Return attach_handle that match all filters sorted by the given
  179         keys. Deleted attach_handle will be returned by default, unless
  180         there's a filter that says otherwise.
  181         """
  182 
  183         if limit == 0:
  184             return []
  185 
  186         query_prefix = model_query(context, models.AttachHandle)
  187         filters = copy.deepcopy(filters)
  188 
  189         exact_match_filter_names = ['uuid', 'id', 'deployable_id', 'cpid_id']
  190 
  191         # Filter the query
  192         query_prefix = self._exact_filter(models.AttachHandle, query_prefix,
  193                                           filters, exact_match_filter_names)
  194         if query_prefix is None:
  195             return []
  196         return _paginate_query(context, models.AttachHandle, query_prefix,
  197                                limit, marker, sort_key, sort_dir)
  198 
  199     def _exact_filter(self, model, query, filters, legal_keys=None):
  200         """Applies exact match filtering to a deployable query.
  201         Returns the updated query.  Modifies filters argument to remove
  202         filters consumed.
  203         :param model: DB model
  204         :param query: query to apply filters to
  205         :param filters: dictionary of filters; values that are lists,
  206                         tuples, sets, or frozensets cause an 'IN' test to
  207                         be performed, while exact matching ('==' operator)
  208                         is used for other values
  209         :param legal_keys: list of keys to apply exact filtering to
  210         """
  211 
  212         if filters is None:
  213             filters = {}
  214         if legal_keys is None:
  215             legal_keys = []
  216         filter_dict = {}
  217 
  218         # Walk through all the keys
  219         for key in legal_keys:
  220             # Skip ones we're not filtering on
  221             if key not in filters:
  222                 continue
  223 
  224             # OK, filtering on this key; what value do we search for?
  225             value = filters.pop(key)
  226 
  227             if isinstance(value, (list, tuple, set, frozenset)):
  228                 if not value:
  229                     return None
  230                 # Looking for values in a list; apply to query directly
  231                 column_attr = getattr(model, key)
  232                 query = query.filter(column_attr.in_(value))
  233             else:
  234                 filter_dict[key] = value
  235         # Apply simple exact matches
  236         if filter_dict:
  237             query = query.filter(*[getattr(model, k) == v
  238                                    for k, v in filter_dict.items()])
  239         return query
  240 
  241     def attach_handle_list(self, context):
  242         query = model_query(context, models.AttachHandle)
  243         return _paginate_query(context, models.AttachHandle, query=query)
  244 
  245     def attach_handle_update(self, context, uuid, values):
  246         if 'uuid' in values:
  247             msg = _("Cannot overwrite UUID for an existing AttachHandle.")
  248             raise exception.InvalidParameterValue(err=msg)
  249         return self._do_update_attach_handle(context, uuid, values)
  250 
  251     @oslo_db_api.retry_on_deadlock
  252     def _do_update_attach_handle(self, context, uuid, values):
  253         with _session_for_write():
  254             query = model_query(context, models.AttachHandle)
  255             query = add_identity_filter(query, uuid)
  256             try:
  257                 ref = query.with_for_update().one()
  258             except NoResultFound:
  259                 raise exception.ResourceNotFound(
  260                     resource='AttachHandle',
  261                     msg='with uuid=%s' % uuid)
  262             ref.update(values)
  263         return ref
  264 
  265     @oslo_db_api.retry_on_deadlock
  266     def _do_allocate_attach_handle(self, context, deployable_id):
  267         """Atomically get a set of attach handles that match the query
  268            and mark one of those as in_use.
  269         """
  270         with _session_for_write() as session:
  271             query = model_query(context, models.AttachHandle). \
  272                 filter_by(deployable_id=deployable_id,
  273                           in_use=False)
  274             values = {"in_use": True}
  275             ref = query.with_for_update().first()
  276             if not ref:
  277                 msg = 'Matching deployable_id {0}'.format(deployable_id)
  278                 raise exception.ResourceNotFound(
  279                     resource='AttachHandle', msg=msg)
  280             ref.update(values)
  281             session.flush()
  282         return ref
  283 
  284     def attach_handle_allocate(self, context, deployable_id):
  285         """Allocate an attach handle with given deployable.
  286 
  287            To allocate is to get an unused resource and mark it as in_use.
  288         """
  289         try:
  290             ah = self._do_allocate_attach_handle(
  291                 context, deployable_id)
  292         except NoResultFound:
  293             msg = 'Matching deployable_id {0}'.format(deployable_id)
  294             raise exception.ResourceNotFound(
  295                 resource='AttachHandle', msg=msg)
  296         return ah
  297 
  298     # NOTE: For deallocate, we use attach_handle_update()
  299 
  300     @oslo_db_api.retry_on_deadlock
  301     def attach_handle_delete(self, context, uuid):
  302         with _session_for_write():
  303             query = model_query(context, models.AttachHandle)
  304             query = add_identity_filter(query, uuid)
  305             count = query.delete()
  306             if count != 1:
  307                 raise exception.ResourceNotFound(
  308                     resource='AttachHandle',
  309                     msg='with uuid=%s' % uuid)
  310 
  311     def control_path_create(self, context, values):
  312         if not values.get('uuid'):
  313             values['uuid'] = uuidutils.generate_uuid()
  314 
  315         control_path_id = models.ControlpathID()
  316         control_path_id.update(values)
  317 
  318         with _session_for_write() as session:
  319             try:
  320                 session.add(control_path_id)
  321                 session.flush()
  322             except db_exc.DBDuplicateEntry:
  323                 raise exception.ControlpathIDAlreadyExists(uuid=values['uuid'])
  324             return control_path_id
  325 
  326     def control_path_get_by_uuid(self, context, uuid):
  327         query = model_query(
  328             context,
  329             models.ControlpathID).filter_by(uuid=uuid)
  330         try:
  331             return query.one()
  332         except NoResultFound:
  333             raise exception.ResourceNotFound(
  334                 resource='ControlpathID',
  335                 msg='with uuid=%s' % uuid)
  336 
  337     def control_path_get_by_filters(self, context,
  338                                     filters, sort_key='created_at',
  339                                     sort_dir='desc', limit=None,
  340                                     marker=None, join_columns=None):
  341         """Return attach_handle that match all filters sorted by the given
  342         keys. Deleted attach_handle will be returned by default, unless
  343         there's a filter that says otherwise.
  344         """
  345 
  346         if limit == 0:
  347             return []
  348 
  349         query_prefix = model_query(context, models.ControlpathID)
  350         filters = copy.deepcopy(filters)
  351 
  352         exact_match_filter_names = ['uuid', 'id', 'device_id', 'cpid_info',
  353                                     'cpid_type']
  354 
  355         # Filter the query
  356         query_prefix = self._exact_filter(models.ControlpathID, query_prefix,
  357                                           filters, exact_match_filter_names)
  358         if query_prefix is None:
  359             return []
  360         return _paginate_query(context, models.ControlpathID, query_prefix,
  361                                limit, marker, sort_key, sort_dir)
  362 
  363     def control_path_list(self, context):
  364         query = model_query(context, models.ControlpathID)
  365         return _paginate_query(context, models.ControlpathID, query=query)
  366 
  367     def control_path_update(self, context, uuid, values):
  368         if 'uuid' in values:
  369             msg = _("Cannot overwrite UUID for an existing ControlpathID.")
  370             raise exception.InvalidParameterValue(err=msg)
  371         return self._do_update_control_path(context, uuid, values)
  372 
  373     @oslo_db_api.retry_on_deadlock
  374     def _do_update_control_path(self, context, uuid, values):
  375         with _session_for_write():
  376             query = model_query(context, models.ControlpathID)
  377             query = add_identity_filter(query, uuid)
  378             try:
  379                 ref = query.with_for_update().one()
  380             except NoResultFound:
  381                 raise exception.ResourceNotFound(
  382                     resource='ControlpathID',
  383                     msg='with uuid=%s' % uuid)
  384             ref.update(values)
  385         return ref
  386 
  387     @oslo_db_api.retry_on_deadlock
  388     def control_path_delete(self, context, uuid):
  389         with _session_for_write():
  390             query = model_query(context, models.ControlpathID)
  391             query = add_identity_filter(query, uuid)
  392             count = query.delete()
  393             if count != 1:
  394                 raise exception.ControlpathNotFound(uuid=uuid)
  395 
  396     def device_create(self, context, values):
  397         if not values.get('uuid'):
  398             values['uuid'] = uuidutils.generate_uuid()
  399 
  400         device = models.Device()
  401         device.update(values)
  402 
  403         with _session_for_write() as session:
  404             try:
  405                 session.add(device)
  406                 session.flush()
  407             except db_exc.DBDuplicateEntry:
  408                 raise exception.DeviceAlreadyExists(uuid=values['uuid'])
  409             return device
  410 
  411     def device_get(self, context, uuid):
  412         query = model_query(
  413             context,
  414             models.Device).filter_by(uuid=uuid)
  415         try:
  416             return query.one()
  417         except NoResultFound:
  418             raise exception.ResourceNotFound(
  419                 resource='Device',
  420                 msg='with uuid=%s' % uuid)
  421 
  422     def device_get_by_id(self, context, id):
  423         query = model_query(
  424             context,
  425             models.Device).filter_by(id=id)
  426         try:
  427             return query.one()
  428         except NoResultFound:
  429             raise exception.ResourceNotFound(
  430                 resource='Device',
  431                 msg='with id=%s' % id)
  432 
  433     def device_list_by_filters(self, context,
  434                                filters, sort_key='created_at',
  435                                sort_dir='desc', limit=None,
  436                                marker=None, join_columns=None):
  437         """Return devices that match all filters sorted by the given keys."""
  438 
  439         if limit == 0:
  440             return []
  441 
  442         query_prefix = model_query(context, models.Device)
  443         filters = copy.deepcopy(filters)
  444 
  445         exact_match_filter_names = ['uuid', 'id', 'type',
  446                                     'vendor', 'model', 'hostname']
  447 
  448         # Filter the query
  449         query_prefix = self._exact_filter(models.Device, query_prefix,
  450                                           filters, exact_match_filter_names)
  451         if query_prefix is None:
  452             return []
  453         return _paginate_query(context, models.Device, query_prefix,
  454                                limit, marker, sort_key, sort_dir)
  455 
  456     def device_list(self, context, limit=None, marker=None, sort_key=None,
  457                     sort_dir=None):
  458         query = model_query(context, models.Device)
  459         return _paginate_query(context, models.Device, query,
  460                                limit, marker, sort_key, sort_dir)
  461 
  462     def device_update(self, context, uuid, values):
  463         if 'uuid' in values:
  464             msg = _("Cannot overwrite UUID for an existing Device.")
  465             raise exception.InvalidParameterValue(err=msg)
  466 
  467         try:
  468             return self._do_update_device(context, uuid, values)
  469         except db_exc.DBDuplicateEntry as e:
  470             if 'name' in e.columns:
  471                 raise exception.DuplicateDeviceName(name=values['name'])
  472 
  473     @oslo_db_api.retry_on_deadlock
  474     def _do_update_device(self, context, uuid, values):
  475         with _session_for_write():
  476             query = model_query(context, models.Device)
  477             query = add_identity_filter(query, uuid)
  478             try:
  479                 ref = query.with_for_update().one()
  480             except NoResultFound:
  481                 raise exception.ResourceNotFound(
  482                     resource='Device',
  483                     msg='with uuid=%s' % uuid)
  484 
  485             ref.update(values)
  486         return ref
  487 
  488     @oslo_db_api.retry_on_deadlock
  489     def device_delete(self, context, uuid):
  490         with _session_for_write():
  491             query = model_query(context, models.Device)
  492             query = add_identity_filter(query, uuid)
  493             count = query.delete()
  494             if count != 1:
  495                 raise exception.ResourceNotFound(
  496                     resource='Device',
  497                     msg='with uuid=%s' % uuid)
  498 
  499     def device_profile_create(self, context, values):
  500         if not values.get('uuid'):
  501             values['uuid'] = uuidutils.generate_uuid()
  502 
  503         device_profile = models.DeviceProfile()
  504         device_profile.update(values)
  505 
  506         with _session_for_write() as session:
  507             try:
  508                 session.add(device_profile)
  509                 session.flush()
  510             except db_exc.DBDuplicateEntry as e:
  511                 # mysql duplicate key error changed as reference link below:
  512                 # https://review.opendev.org/c/openstack/oslo.db/+/792124
  513                 LOG.info('Duplicate columns are: ', e.columns)
  514                 columns = [column.split('0')[1] if 'uniq_' in column else
  515                            column for column in e.columns]
  516                 if 'name' in columns:
  517                     raise exception.DuplicateDeviceProfileName(
  518                         name=values['name'])
  519                 else:
  520                     raise exception.DeviceProfileAlreadyExists(
  521                         uuid=values['uuid'])
  522             return device_profile
  523 
  524     def device_profile_get_by_uuid(self, context, uuid):
  525         query = model_query(
  526             context,
  527             models.DeviceProfile).filter_by(uuid=uuid)
  528         try:
  529             return query.one()
  530         except NoResultFound:
  531             raise exception.ResourceNotFound(
  532                 resource='Device Profile',
  533                 msg='with uuid=%s' % uuid)
  534 
  535     def device_profile_get_by_id(self, context, id):
  536         query = model_query(
  537             context,
  538             models.DeviceProfile).filter_by(id=id)
  539         try:
  540             return query.one()
  541         except NoResultFound:
  542             raise exception.ResourceNotFound(
  543                 resource='Device Profile',
  544                 msg='with id=%s' % id)
  545 
  546     def device_profile_get(self, context, name):
  547         query = model_query(
  548             context, models.DeviceProfile).filter_by(name=name)
  549         try:
  550             return query.one()
  551         except NoResultFound:
  552             raise exception.ResourceNotFound(
  553                 resource='Device Profile',
  554                 msg='with name=%s' % name)
  555 
  556     def device_profile_list_by_filters(
  557             self, context, filters, sort_key='created_at', sort_dir='desc',
  558             limit=None, marker=None, join_columns=None):
  559         if limit == 0:
  560             return []
  561 
  562         query_prefix = model_query(context, models.DeviceProfile)
  563         filters = copy.deepcopy(filters)
  564 
  565         exact_match_filter_names = ['uuid', 'id', 'name']
  566 
  567         # Filter the query
  568         query_prefix = self._exact_filter(models.DeviceProfile, query_prefix,
  569                                           filters, exact_match_filter_names)
  570         if query_prefix is None:
  571             return []
  572         return _paginate_query(context, models.DeviceProfile, query_prefix,
  573                                limit, marker, sort_key, sort_dir)
  574 
  575     def device_profile_list(self, context):
  576         query = model_query(context, models.DeviceProfile)
  577         return _paginate_query(context, models.DeviceProfile, query=query)
  578 
  579     def device_profile_update(self, context, uuid, values):
  580         if 'uuid' in values:
  581             msg = _("Cannot overwrite UUID for an existing DeviceProfile.")
  582             raise exception.InvalidParameterValue(err=msg)
  583 
  584         try:
  585             return self._do_update_device_profile(context, uuid, values)
  586         except db_exc.DBDuplicateEntry as e:
  587             if 'name' in e.columns:
  588                 raise exception.DuplicateDeviceProfileName(name=values['name'])
  589 
  590     @oslo_db_api.retry_on_deadlock
  591     def _do_update_device_profile(self, context, uuid, values):
  592         with _session_for_write():
  593             query = model_query(context, models.DeviceProfile)
  594             query = add_identity_filter(query, uuid)
  595             try:
  596                 ref = query.with_for_update().one()
  597             except NoResultFound:
  598                 raise exception.ResourceNotFound(
  599                     resource='Device Profile',
  600                     msg='with uuid=%s' % uuid)
  601 
  602             ref.update(values)
  603         return ref
  604 
  605     @oslo_db_api.retry_on_deadlock
  606     def device_profile_delete(self, context, uuid):
  607         with _session_for_write():
  608             query = model_query(context, models.DeviceProfile)
  609             query = add_identity_filter(query, uuid)
  610             count = query.delete()
  611             if count != 1:
  612                 raise exception.ResourceNotFound(
  613                     resource='Device Profile',
  614                     msg='with uuid=%s' % uuid)
  615 
  616     def deployable_create(self, context, values):
  617         if not values.get('uuid'):
  618             values['uuid'] = uuidutils.generate_uuid()
  619         if values.get('id'):
  620             values.pop('id', None)
  621         deployable = models.Deployable()
  622         deployable.update(values)
  623 
  624         with _session_for_write() as session:
  625             try:
  626                 session.add(deployable)
  627                 session.flush()
  628             except db_exc.DBDuplicateEntry:
  629                 raise exception.DeployableAlreadyExists(uuid=values['uuid'])
  630             return deployable
  631 
  632     def deployable_get(self, context, uuid):
  633         query = model_query(
  634             context,
  635             models.Deployable).filter_by(uuid=uuid)
  636         try:
  637             return query.one()
  638         except NoResultFound:
  639             raise exception.ResourceNotFound(
  640                 resource='Deployable',
  641                 msg='with uuid=%s' % uuid)
  642 
  643     def deployable_get_by_rp_uuid(self, context, rp_uuid):
  644         """Get a deployable by resource provider UUID."""
  645         query = model_query(
  646             context,
  647             models.Deployable).filter_by(rp_uuid=rp_uuid)
  648         try:
  649             return query.one()
  650         except NoResultFound:
  651             raise exception.ResourceNotFound(
  652                 resource='Deployable',
  653                 msg='with resource provider uuid=%s' % rp_uuid)
  654 
  655     def deployable_list(self, context):
  656         query = model_query(context, models.Deployable)
  657         return query.all()
  658 
  659     def deployable_update(self, context, uuid, values):
  660         if 'uuid' in values:
  661             msg = _("Cannot overwrite UUID for an existing Deployable.")
  662             raise exception.InvalidParameterValue(err=msg)
  663 
  664         try:
  665             return self._do_update_deployable(context, uuid, values)
  666         except db_exc.DBDuplicateEntry as e:
  667             if 'name' in e.columns:
  668                 raise exception.DuplicateDeployableName(name=values['name'])
  669 
  670     @oslo_db_api.retry_on_deadlock
  671     def _do_update_deployable(self, context, uuid, values):
  672         with _session_for_write():
  673             query = model_query(context, models.Deployable)
  674             # query = add_identity_filter(query, uuid)
  675             query = query.filter_by(uuid=uuid)
  676             try:
  677                 ref = query.with_for_update().one()
  678             except NoResultFound:
  679                 raise exception.ResourceNotFound(
  680                     resource='Deployable',
  681                     msg='with uuid=%s' % uuid)
  682 
  683             ref.update(values)
  684         return ref
  685 
  686     @oslo_db_api.retry_on_deadlock
  687     def deployable_delete(self, context, uuid):
  688         with _session_for_write():
  689             query = model_query(context, models.Deployable)
  690             query = add_identity_filter(query, uuid)
  691             query.update({'root_id': None})
  692             count = query.delete()
  693             if count != 1:
  694                 raise exception.ResourceNotFound(
  695                     resource='Deployable',
  696                     msg='with uuid=%s' % uuid)
  697 
  698     def deployable_get_by_filters(self, context,
  699                                   filters, sort_key='created_at',
  700                                   sort_dir='desc', limit=None,
  701                                   marker=None, join_columns=None):
  702         """Return list of deployables matching all filters sorted by
  703         the sort_key. See deployable_get_by_filters_sort for
  704         more information.
  705         """
  706         return self.deployable_get_by_filters_sort(context, filters,
  707                                                    limit=limit, marker=marker,
  708                                                    join_columns=join_columns,
  709                                                    sort_key=sort_key,
  710                                                    sort_dir=sort_dir)
  711 
  712     def deployable_get_by_filters_sort(self, context, filters, limit=None,
  713                                        marker=None, join_columns=None,
  714                                        sort_key=None, sort_dir=None):
  715         """Return deployables that match all filters sorted by the given
  716         keys. Deleted deployables will be returned by default, unless
  717         there's a filter that says otherwise.
  718         """
  719         if limit == 0:
  720             return []
  721 
  722         query_prefix = model_query(context, models.Deployable)
  723         filters = copy.deepcopy(filters)
  724 
  725         exact_match_filter_names = ['id', 'uuid', 'name',
  726                                     'parent_id', 'root_id',
  727                                     'num_accelerators', 'device_id',
  728                                     'driver_name', 'rp_uuid', 'bitstream_id']
  729 
  730         # Filter the query
  731         query_prefix = self._exact_filter(models.Deployable, query_prefix,
  732                                           filters,
  733                                           exact_match_filter_names)
  734         if query_prefix is None:
  735             return []
  736         return _paginate_query(context, models.Deployable, query_prefix,
  737                                limit, marker, sort_key, sort_dir)
  738 
  739     def attribute_create(self, context, values):
  740         if not values.get('uuid'):
  741             values['uuid'] = uuidutils.generate_uuid()
  742         if values.get('id'):
  743             values.pop('id', None)
  744         attribute = models.Attribute()
  745         attribute.update(values)
  746 
  747         with _session_for_write() as session:
  748             try:
  749                 session.add(attribute)
  750                 session.flush()
  751             except db_exc.DBDuplicateEntry:
  752                 raise exception.AttributeAlreadyExists(
  753                     uuid=values['uuid'])
  754             return attribute
  755 
  756     def attribute_get(self, context, uuid):
  757         query = model_query(
  758             context,
  759             models.Attribute).filter_by(uuid=uuid)
  760         try:
  761             return query.one()
  762         except NoResultFound:
  763             raise exception.ResourceNotFound(
  764                 resource='Attribute',
  765                 msg='with uuid=%s' % uuid)
  766 
  767     def attribute_get_by_deployable_id(self, context, deployable_id):
  768         query = model_query(
  769             context,
  770             models.Attribute).filter_by(deployable_id=deployable_id)
  771         return query.all()
  772 
  773     def attribute_get_by_filter(self, context, filters):
  774         """Return attributes that matches the filters
  775         """
  776         query_prefix = model_query(context, models.Attribute)
  777         exact_match_filter_names = ['deployable_id', 'key']
  778 
  779         # Filter the query
  780         query_prefix = self._exact_filter(models.Attribute, query_prefix,
  781                                           filters, exact_match_filter_names)
  782         if query_prefix is None:
  783             return []
  784 
  785         return query_prefix.all()
  786 
  787     # def _exact_attribute_by_filter(self, query, filters):
  788     #     """Applies exact match filtering to a atrtribute query.
  789     #     Returns the updated query.
  790     #     :param filters: The filters specified by a dict of kv pairs
  791     #     """
  792     #
  793     #     model = models.Attribute
  794     #     filter_dict = filters
  795     #
  796     #     # Apply simple exact matches
  797     #     query = query.filter(*[getattr(models.Attribute, k) == v
  798     #                            for k, v in filter_dict.items()])
  799     #     return query
  800 
  801     def attribute_update(self, context, uuid, key, value):
  802         return self._do_update_attribute(context, uuid, key, value)
  803 
  804     @oslo_db_api.retry_on_deadlock
  805     def _do_update_attribute(self, context, uuid, key, value):
  806         update_fields = {'key': key, 'value': value}
  807         with _session_for_write():
  808             query = model_query(context, models.Attribute)
  809             query = add_identity_filter(query, uuid)
  810             try:
  811                 ref = query.with_for_update().one()
  812             except NoResultFound:
  813                 raise exception.ResourceNotFound(
  814                     resource='Attribute',
  815                     msg='with uuid=%s' % uuid)
  816 
  817             ref.update(update_fields)
  818         return ref
  819 
  820     def attribute_delete(self, context, uuid):
  821         with _session_for_write():
  822             query = model_query(context, models.Attribute)
  823             query = add_identity_filter(query, uuid)
  824             count = query.delete()
  825             if count != 1:
  826                 raise exception.ResourceNotFound(
  827                     resource='Attribute',
  828                     msg='with uuid=%s' % uuid)
  829 
  830     def extarq_create(self, context, values):
  831         if not values.get('uuid'):
  832             values['uuid'] = uuidutils.generate_uuid()
  833         if values.get('id'):
  834             values.pop('id', None)
  835 
  836         if values.get('device_profile_id'):
  837             pass  # Already have the devprof id, so nothing to do
  838         elif values.get('device_profile_name'):
  839             devprof = self.device_profile_get(context,
  840                                               values['device_profile_name'])
  841             values['device_profile_id'] = devprof['id']
  842         else:
  843             raise exception.DeviceProfileNameNeeded()
  844 
  845         extarq = models.ExtArq()
  846         extarq.update(values)
  847 
  848         with _session_for_write() as session:
  849             try:
  850                 session.add(extarq)
  851                 session.flush()
  852             except db_exc.DBDuplicateEntry:
  853                 raise exception.ExtArqAlreadyExists(uuid=values['uuid'])
  854             return extarq
  855 
  856     @oslo_db_api.retry_on_deadlock
  857     def extarq_delete(self, context, uuid):
  858         with _session_for_write():
  859             query = model_query(context, models.ExtArq)
  860             query = add_identity_filter(query, uuid)
  861             count = query.delete()
  862             if count != 1:
  863                 raise exception.ResourceNotFound(
  864                     resource='ExtArq',
  865                     msg='with uuid=%s' % uuid)
  866 
  867     def extarq_update(self, context, uuid, values, state_scope=None):
  868         if 'uuid' in values and values['uuid'] != uuid:
  869             msg = _("Cannot overwrite UUID for an existing ExtArq.")
  870             raise exception.InvalidParameterValue(err=msg)
  871         return self._do_update_extarq(context, uuid, values, state_scope)
  872 
  873     @oslo_db_api.retry_on_deadlock
  874     def _do_update_extarq(self, context, uuid, values, state_scope=None):
  875         with _session_for_write():
  876             query = model_query(context, models.ExtArq)
  877             query = query_update = query.filter_by(
  878                 uuid=uuid).with_for_update()
  879             if type(state_scope) is list:
  880                 query_update = query_update.filter(
  881                     models.ExtArq.state.in_(state_scope))
  882             try:
  883                 query_update.update(
  884                     values, synchronize_session="fetch")
  885             except NoResultFound:
  886                 raise exception.ResourceNotFound(
  887                     resource='ExtArq',
  888                     msg='with uuid=%s' % uuid)
  889             ref = query.first()
  890         return ref
  891 
  892     def extarq_list(self, context, uuid_range=None):
  893         query = model_query(context, models.ExtArq)
  894         if type(uuid_range) is list:
  895             query = query.filter(
  896                 models.ExtArq.uuid.in_(uuid_range))
  897         return _paginate_query(context, models.ExtArq, query)
  898 
  899     @oslo_db_api.retry_on_deadlock
  900     def extarq_get(self, context, uuid, lock=False):
  901         query = model_query(
  902             context,
  903             models.ExtArq).filter_by(uuid=uuid)
  904         # NOTE we will support aync bind, so get query by lock
  905         if lock:
  906             query = query.with_for_update()
  907         try:
  908             return query.one()
  909         except NoResultFound:
  910             raise exception.ResourceNotFound(
  911                 resource='ExtArq',
  912                 msg='with uuid=%s' % uuid)
  913 
  914     def _get_quota_usages(self, context, project_id, resources=None):
  915         # Broken out for testability
  916         query = model_query(context, models.QuotaUsage,).filter_by(
  917             project_id=project_id)
  918         if resources:
  919             query = query.filter(models.QuotaUsage.resource.in_(
  920                 list(resources)))
  921         rows = query.order_by(models.QuotaUsage.id.asc()). \
  922             with_for_update().all()
  923         return {row.resource: row for row in rows}
  924 
  925     def _quota_usage_create(self, project_id, resource, until_refresh,
  926                             in_use, reserved, session=None):
  927 
  928         quota_usage_ref = models.QuotaUsage()
  929         quota_usage_ref.project_id = project_id
  930         quota_usage_ref.resource = resource
  931         quota_usage_ref.in_use = in_use
  932         quota_usage_ref.reserved = reserved
  933         quota_usage_ref.until_refresh = until_refresh
  934         quota_usage_ref.save(session=session)
  935 
  936         return quota_usage_ref
  937 
  938     def _reservation_create(self, uuid, usage, project_id, resource, delta,
  939                             expire, session=None):
  940         usage_id = usage['id'] if usage else None
  941         reservation_ref = models.Reservation()
  942         reservation_ref.uuid = uuid
  943         reservation_ref.usage_id = usage_id
  944         reservation_ref.project_id = project_id
  945         reservation_ref.resource = resource
  946         reservation_ref.delta = delta
  947         reservation_ref.expire = expire
  948         reservation_ref.save(session=session)
  949         return reservation_ref
  950 
  951     def _get_reservation_resources(self, context, reservation_ids):
  952         """Return the relevant resources by reservations."""
  953 
  954         reservations = model_query(context, models.Reservation). \
  955             options(load_only('resource')). \
  956             filter(models.Reservation.uuid.in_(reservation_ids)). \
  957             all()
  958         return {r.resource for r in reservations}
  959 
  960     def _quota_reservations(self, session, context, reservations):
  961         """Return the relevant reservations."""
  962 
  963         # Get the listed reservations
  964         return model_query(context, models.Reservation). \
  965             filter(models.Reservation.uuid.in_(reservations)). \
  966             with_for_update(). \
  967             all()
  968 
  969     def quota_reserve(self, context, resources, deltas, expire,
  970                       until_refresh, max_age, project_id=None,
  971                       is_allocated_reserve=False):
  972         """Create reservation record in DB according to params"""
  973         with _session_for_write() as session:
  974             if project_id is None:
  975                 project_id = context.project_id
  976             usages = self._get_quota_usages(context, project_id,
  977                                             resources=deltas.keys())
  978             work = set(deltas.keys())
  979             while work:
  980                 resource = work.pop()
  981 
  982                 # Do we need to refresh the usage?
  983                 refresh = False
  984                 # create quota usage in DB if there is no record of this type
  985                 # of resource
  986                 if resource not in usages:
  987                     usages[resource] = self._quota_usage_create(
  988                         project_id, resource, until_refresh or None,
  989                         in_use=0, reserved=0, session=session)
  990                     refresh = True
  991                 elif usages[resource].in_use < 0:
  992                     # Negative in_use count indicates a desync, so try to
  993                     # heal from that...
  994                     refresh = True
  995                 elif usages[resource].until_refresh is not None:
  996                     usages[resource].until_refresh -= 1
  997                     if usages[resource].until_refresh <= 0:
  998                         refresh = True
  999                 elif max_age and usages[resource].updated_at is not None and (
 1000                     (timeutils.utcnow() -
 1001                         usages[resource].updated_at).total_seconds() >=
 1002                         max_age):
 1003                     refresh = True
 1004 
 1005                 # refresh the usage
 1006                 if refresh:
 1007                     # Grab the sync routine
 1008                     updates = self._sync_acc_res(context,
 1009                                                  resource, project_id)
 1010                     for res, in_use in updates.items():
 1011                         # Make sure we have a destination for the usage!
 1012                         if res not in usages:
 1013                             usages[res] = self._quota_usage_create(
 1014                                 project_id,
 1015                                 res,
 1016                                 until_refresh or None,
 1017                                 in_use=0,
 1018                                 reserved=0,
 1019                                 session=session
 1020                             )
 1021 
 1022                         # Update the usage
 1023                         usages[res].in_use = in_use
 1024                         usages[res].until_refresh = until_refresh or None
 1025 
 1026                         # Because more than one resource may be refreshed
 1027                         # by the call to the sync routine, and we don't
 1028                         # want to double-sync, we make sure all refreshed
 1029                         # resources are dropped from the work set.
 1030                         work.discard(res)
 1031 
 1032                         # NOTE(Vek): We make the assumption that the sync
 1033                         #            routine actually refreshes the
 1034                         #            resources that it is the sync routine
 1035                         #            for.  We don't check, because this is
 1036                         #            a best-effort mechanism.
 1037 
 1038             unders = [r for r, delta in deltas.items()
 1039                       if delta < 0 and delta + usages[r].in_use < 0]
 1040             reservations = []
 1041             for resource, delta in deltas.items():
 1042                 usage = usages[resource]
 1043                 reservation = self._reservation_create(
 1044                     str(uuid.uuid4()), usage, project_id, resource,
 1045                     delta, expire, session=session)
 1046                 reservations.append(reservation.uuid)
 1047                 usages[resource].reserved += delta
 1048             session.flush()
 1049         if unders:
 1050             LOG.warning("Change will make usage less than 0 for the "
 1051                         "following resources: %s", unders)
 1052         return reservations
 1053 
 1054     def _sync_acc_res(self, context, resource, project_id):
 1055         """Quota sync funciton"""
 1056         res_in_use = self._device_data_get_for_project(context, resource,
 1057                                                        project_id)
 1058         return {resource: res_in_use}
 1059 
 1060     def _device_data_get_for_project(self, context, resource, project_id):
 1061         """Return the number of resource which is being used by a project"""
 1062         query = model_query(context, models.Device).filter_by(type=resource)
 1063 
 1064         return query.count()
 1065 
 1066     def _dict_with_usage_id(self, usages):
 1067         return {row.id: row for row in usages.values()}
 1068 
 1069     def reservation_commit(self, context, reservations, project_id=None):
 1070         """Commit quota reservation to quota usage table"""
 1071         with _session_for_write() as session:
 1072             quota_usage = self._get_quota_usages(
 1073                 context, project_id,
 1074                 resources=self._get_reservation_resources(context,
 1075                                                           reservations))
 1076             usages = self._dict_with_usage_id(quota_usage)
 1077 
 1078             for reservation in self._quota_reservations(session, context,
 1079                                                         reservations):
 1080 
 1081                 usage = usages[reservation.usage_id]
 1082                 if reservation.delta >= 0:
 1083                     usage.reserved -= reservation.delta
 1084                 usage.in_use += reservation.delta
 1085                 session.flush()
 1086                 reservation.delete(session=session)
 1087 
 1088     def process_sort_params(self, sort_keys, sort_dirs,
 1089                             default_keys=['created_at', 'id'],
 1090                             default_dir='asc'):
 1091 
 1092         # Determine direction to use for when adding default keys
 1093         if sort_dirs and len(sort_dirs) != 0:
 1094             default_dir_value = sort_dirs[0]
 1095         else:
 1096             default_dir_value = default_dir
 1097 
 1098         # Create list of keys (do not modify the input list)
 1099         if sort_keys:
 1100             result_keys = list(sort_keys)
 1101         else:
 1102             result_keys = []
 1103 
 1104         # If a list of directions is not provided,
 1105         # use the default sort direction for all provided keys
 1106         if sort_dirs:
 1107             result_dirs = []
 1108             # Verify sort direction
 1109             for sort_dir in sort_dirs:
 1110                 if sort_dir not in ('asc', 'desc'):
 1111                     msg = _("Unknown sort direction, must be 'desc' or 'asc'")
 1112                     raise exception.InvalidInput(reason=msg)
 1113                 result_dirs.append(sort_dir)
 1114         else:
 1115             result_dirs = [default_dir_value for _sort_key in result_keys]
 1116 
 1117         # Ensure that the key and direction length match
 1118         while len(result_dirs) < len(result_keys):
 1119             result_dirs.append(default_dir_value)
 1120         # Unless more direction are specified, which is an error
 1121         if len(result_dirs) > len(result_keys):
 1122             msg = _("Sort direction size exceeds sort key size")
 1123             raise exception.InvalidInput(reason=msg)
 1124 
 1125         # Ensure defaults are included
 1126         for key in default_keys:
 1127             if key not in result_keys:
 1128                 result_keys.append(key)
 1129                 result_dirs.append(default_dir_value)
 1130 
 1131         return result_keys, result_dirs