Implement package search in repository API

* Add Search from packages

Partially-implements blueprint murano-repository-api-v2
Partially-implements blueprint app-catalog-search
Change-Id: I53dbf3d00e75ff3ae5c46623f1873a3a99c6a777
This commit is contained in:
Ekaterina Fedorova 2014-03-27 17:39:43 +04:00
parent 3621c57404
commit 39c2726540
4 changed files with 152 additions and 0 deletions

View File

@ -17,6 +17,15 @@ from muranoapi.db import session as db_session
stats = None
SUPPORTED_PARAMS = ('order_by', 'category', 'marker', 'tag', 'class',
'limit', 'type', 'fqn', 'category', 'owned')
LIST_PARAMS = ('category', 'tag', 'class', 'order_by')
ORDER_VALUES = ('fqn', 'name', 'created')
SEARCH_MAPPING = {'fqn': 'fully_qualified_name',
'name': 'name',
'created': 'created'
}
def get_draft(environment_id=None, session_id=None):
unit = db_session.get_session()

View File

@ -16,6 +16,7 @@
from oslo.config import cfg
from webob import exc
import muranoapi.api.v1
from muranoapi.db.catalog import api as db_api
from muranoapi.openstack.common import exception
from muranoapi.openstack.common.gettextutils import _ # noqa
@ -26,6 +27,10 @@ from muranoapi.openstack.common import wsgi
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
SUPPORTED_PARAMS = muranoapi.api.v1.SUPPORTED_PARAMS
LIST_PARAMS = muranoapi.api.v1.LIST_PARAMS
ORDER_VALUES = muranoapi.api.v1.ORDER_VALUES
def _check_content_type(req, content_type):
try:
@ -36,6 +41,30 @@ def _check_content_type(req, content_type):
raise exc.HTTPBadRequest(explanation=msg)
def _get_filters(query_params):
filters = {}
for param_pair in query_params:
k, v = param_pair
if k not in SUPPORTED_PARAMS:
LOG.warning(_("Search by parameter '{name}' "
"is not supported. Skipping it.").format(name=k))
continue
if k in LIST_PARAMS:
filters.setdefault(k, []).append(v)
else:
filters[k] = v
order_by = filters.get('order_by', [])
for i in order_by[:]:
if ORDER_VALUES and i not in ORDER_VALUES:
filters['order_by'].remove(i)
LOG.warning(_("Value of 'order_by' parameter is not valid. "
"Allowed values are: {0}. Skipping it.").format(
", ".join(ORDER_VALUES)))
return filters
class Controller(object):
"""
WSGI controller for application catalog resource in Murano v1 API
@ -66,6 +95,11 @@ class Controller(object):
package = db_api.package_get(package_id, req.context)
return package.to_dict()
def search(self, req):
filters = _get_filters(req.GET._items)
packages = db_api.package_search(filters, req.context)
return {"packages": [package.to_dict() for package in packages]}
def create_resource():
return wsgi.Resource(Controller())

View File

@ -129,4 +129,8 @@ class API(wsgi.Router):
controller=catalog_resource,
action='update',
conditions={'method': ['PATCH']})
mapper.connect('/catalog/packages',
controller=catalog_resource,
action='search',
conditions={'method': ['GET']})
super(API, self).__init__(mapper)

View File

@ -12,13 +12,17 @@
# License for the specific language governing permissions and limitations
# under the License.
from sqlalchemy import or_
from sqlalchemy import sql
from webob import exc
import muranoapi
from muranoapi.db import models
from muranoapi.db import session as db_session
from muranoapi.openstack.common.gettextutils import _ # noqa
from muranoapi.openstack.common import log as logging
SEARCH_MAPPING = muranoapi.api.v1.SEARCH_MAPPING
LOG = logging.getLogger(__name__)
@ -204,3 +208,104 @@ def package_update(pkg_id, changes, context):
with session.begin():
session.add(pkg)
return pkg
def package_search(filters, context):
"""
Search packages with different filters
* Admin is allowed to browse all the packages
* Regular user is allowed to browse all packages belongs to user tenant
and all other packages marked is_public.
Also all packages should be enabled.
* Use marker and limit for pagination:
The typical pattern of limit and marker is to make an initial limited
request and then to use the ID of the last package from the response
as the marker parameter in a subsequent limited request.
"""
def _validate_limit(value):
if value is None:
return
try:
value = int(value)
except ValueError:
msg = _("limit param must be an integer")
raise exc.HTTPBadRequest(explanation=msg)
if value < 0:
msg = _("limit param must be positive")
raise exc.HTTPBadRequest(explanation=msg)
return value
def get_pkg_attr(package_obj, search_attr_name):
return getattr(package_obj, SEARCH_MAPPING[search_attr_name])
limit = _validate_limit(filters.get('limit'))
session = db_session.get_session()
pkg = models.Package
if 'owned' in filters.keys():
query = session.query(pkg).filter(pkg.owner_id == context.tenant)
elif context.is_admin:
query = session.query(pkg)
else:
query = session.query(pkg).filter(or_((pkg.is_public & pkg.enabled),
pkg.owner_id == context.tenant))
if 'category' in filters.keys():
query = query.filter(pkg.categories.any(
models.Category.name.in_(filters['category'])))
if 'tag' in filters.keys():
query = query.filter(pkg.tags.any(
models.Tag.name.in_(filters['tag'])))
if 'class' in filters.keys():
query = query.filter(pkg.class_definition.any(
models.Class.name.in_(filters['class'])))
if 'fqn' in filters.keys():
query = query.filter(pkg.fully_qualified_name == filters['fqn'])
sort_keys = filters.get('order_by', []) or ['created']
for key in sort_keys:
query = query.order_by(get_pkg_attr(pkg, key))
marker = filters.get('marker')
if marker is not None:
# Note(efedorova): Copied from Glance
# Pagination works by requiring a unique sort_key, specified by sort_
# keys. (If sort_keys is not unique, then we risk looping through
# values.) We use the last row in the previous page as the 'marker'
# for pagination. So we must return values that follow the passed
# marker in the order. With a single-valued sort_key, this would
# be easy: sort_key > X. With a compound-values sort_key,
# (k1, k2, k3) we must do this to repeat the lexicographical
# ordering: (k1 > X1) or (k1 == X1 && k2 > X2) or
# (k1 == X1 && k2 == X2 && k3 > X3)
model_marker = _package_get(marker, session)
marker_values = []
for sort_key in sort_keys:
v = getattr(model_marker, sort_key)
marker_values.append(v)
# Build up an array of sort criteria as in the docstring
criteria_list = []
for i in range(len(sort_keys)):
crit_attrs = []
for j in range(i):
model_attr = get_pkg_attr(pkg, sort_keys[j])
crit_attrs.append((model_attr == marker_values[j]))
model_attr = get_pkg_attr(pkg, sort_keys[i])
crit_attrs.append((model_attr > marker_values[i]))
criteria = sql.and_(*crit_attrs)
criteria_list.append(criteria)
f = sql.or_(*criteria_list)
query = query.filter(f)
if limit is not None:
query = query.limit(limit)
return query.all()