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:
parent
3621c57404
commit
39c2726540
@ -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()
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user