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
|
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):
|
def get_draft(environment_id=None, session_id=None):
|
||||||
unit = db_session.get_session()
|
unit = db_session.get_session()
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
from webob import exc
|
from webob import exc
|
||||||
|
|
||||||
|
import muranoapi.api.v1
|
||||||
from muranoapi.db.catalog import api as db_api
|
from muranoapi.db.catalog import api as db_api
|
||||||
from muranoapi.openstack.common import exception
|
from muranoapi.openstack.common import exception
|
||||||
from muranoapi.openstack.common.gettextutils import _ # noqa
|
from muranoapi.openstack.common.gettextutils import _ # noqa
|
||||||
@ -26,6 +27,10 @@ from muranoapi.openstack.common import wsgi
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
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):
|
def _check_content_type(req, content_type):
|
||||||
try:
|
try:
|
||||||
@ -36,6 +41,30 @@ def _check_content_type(req, content_type):
|
|||||||
raise exc.HTTPBadRequest(explanation=msg)
|
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):
|
class Controller(object):
|
||||||
"""
|
"""
|
||||||
WSGI controller for application catalog resource in Murano v1 API
|
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)
|
package = db_api.package_get(package_id, req.context)
|
||||||
return package.to_dict()
|
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():
|
def create_resource():
|
||||||
return wsgi.Resource(Controller())
|
return wsgi.Resource(Controller())
|
||||||
|
@ -129,4 +129,8 @@ class API(wsgi.Router):
|
|||||||
controller=catalog_resource,
|
controller=catalog_resource,
|
||||||
action='update',
|
action='update',
|
||||||
conditions={'method': ['PATCH']})
|
conditions={'method': ['PATCH']})
|
||||||
|
mapper.connect('/catalog/packages',
|
||||||
|
controller=catalog_resource,
|
||||||
|
action='search',
|
||||||
|
conditions={'method': ['GET']})
|
||||||
super(API, self).__init__(mapper)
|
super(API, self).__init__(mapper)
|
||||||
|
@ -12,13 +12,17 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from sqlalchemy import or_
|
||||||
|
from sqlalchemy import sql
|
||||||
from webob import exc
|
from webob import exc
|
||||||
|
|
||||||
|
import muranoapi
|
||||||
from muranoapi.db import models
|
from muranoapi.db import models
|
||||||
from muranoapi.db import session as db_session
|
from muranoapi.db import session as db_session
|
||||||
from muranoapi.openstack.common.gettextutils import _ # noqa
|
from muranoapi.openstack.common.gettextutils import _ # noqa
|
||||||
from muranoapi.openstack.common import log as logging
|
from muranoapi.openstack.common import log as logging
|
||||||
|
|
||||||
|
SEARCH_MAPPING = muranoapi.api.v1.SEARCH_MAPPING
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -204,3 +208,104 @@ def package_update(pkg_id, changes, context):
|
|||||||
with session.begin():
|
with session.begin():
|
||||||
session.add(pkg)
|
session.add(pkg)
|
||||||
return 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