Implement the GET and DELETE RESTAPI of resource plans
Change-Id: Ibe5e37801c7a89a1137cedea2a5f11261912b851 Closes-Bug: #1541729
This commit is contained in:
parent
2ae80a66a5
commit
78244d1e0a
@ -8,5 +8,6 @@
|
||||
"plan:create": "rule:admin_or_owner",
|
||||
"plan:update": "rule:admin_or_owner",
|
||||
"plan:delete": "rule:admin_or_owner",
|
||||
"plan:get": "rule:admin_or_owner"
|
||||
"plan:get": "rule:admin_or_owner",
|
||||
"plan:get_all": "rule:admin_or_owner"
|
||||
}
|
||||
|
@ -12,18 +12,34 @@
|
||||
|
||||
"""The plans api."""
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import webob
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from webob import exc
|
||||
|
||||
import smaug
|
||||
from smaug.api import common
|
||||
from smaug.api.openstack import wsgi
|
||||
from smaug import exception
|
||||
from smaug.i18n import _, _LI
|
||||
|
||||
from smaug import objects
|
||||
from smaug.objects import base as objects_base
|
||||
from smaug.operationengine import api as operationengine_api
|
||||
import smaug.policy
|
||||
from smaug import utils
|
||||
|
||||
import six
|
||||
|
||||
query_plan_filters_opt = cfg.ListOpt('query_plan_filters',
|
||||
default=['name', 'status'],
|
||||
help="Plan filter options which "
|
||||
"non-admin user could use to "
|
||||
"query plans. Default values "
|
||||
"are: ['name', 'status']")
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opt(query_plan_filters_opt)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -45,7 +61,7 @@ def check_policy(context, action, target_obj=None):
|
||||
smaug.policy.enforce(context, _action, target)
|
||||
|
||||
|
||||
class PlanViewBuilder(object):
|
||||
class PlanViewBuilder(common.ViewBuilder):
|
||||
"""Model a server API response as a python dictionary."""
|
||||
|
||||
_collection_name = "plans"
|
||||
@ -67,6 +83,36 @@ class PlanViewBuilder(object):
|
||||
}
|
||||
return plan_ref
|
||||
|
||||
def detail_list(self, request, plans, plan_count=None):
|
||||
"""Detailed view of a list of plans."""
|
||||
return self._list_view(self.detail, request, plans,
|
||||
plan_count,
|
||||
self._collection_name)
|
||||
|
||||
def _list_view(self, func, request, plans, plan_count,
|
||||
coll_name=_collection_name):
|
||||
"""Provide a view for a list of plans.
|
||||
|
||||
:param func: Function used to format the plan data
|
||||
:param request: API request
|
||||
:param plans: List of plans in dictionary format
|
||||
:param plan_count: Length of the original list of plans
|
||||
:param coll_name: Name of collection, used to generate the next link
|
||||
for a pagination query
|
||||
:returns: Plan data in dictionary format
|
||||
"""
|
||||
plans_list = [func(request, plan)['plan'] for plan in plans]
|
||||
plans_links = self._get_collection_links(request,
|
||||
plans,
|
||||
coll_name,
|
||||
plan_count)
|
||||
plans_dict = {}
|
||||
plans_dict['plans'] = plans_list
|
||||
if plans_links:
|
||||
plans_dict['plans_links'] = plans_links
|
||||
|
||||
return plans_dict
|
||||
|
||||
|
||||
class PlansController(wsgi.Controller):
|
||||
"""The Plans API controller for the OpenStack API."""
|
||||
@ -80,9 +126,21 @@ class PlansController(wsgi.Controller):
|
||||
def show(self, req, id):
|
||||
"""Return data about the given plan."""
|
||||
context = req.environ['smaug.context']
|
||||
|
||||
LOG.info(_LI("Show plan with id: %s"), id, context=context)
|
||||
# TODO(chenying)
|
||||
return {'Smaug': "Plans show test."}
|
||||
|
||||
if not uuidutils.is_uuid_like(id):
|
||||
msg = _("Invalid plan id provided.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
try:
|
||||
plan = self._plan_get(context, id)
|
||||
except exception.PlanNotFound as error:
|
||||
raise exc.HTTPNotFound(explanation=error.msg)
|
||||
|
||||
LOG.info(_LI("Show plan request issued successfully."),
|
||||
resource={'id': plan.id})
|
||||
return self._view_builder.detail(req, plan)
|
||||
|
||||
def delete(self, req, id):
|
||||
"""Delete a plan."""
|
||||
@ -90,22 +148,86 @@ class PlansController(wsgi.Controller):
|
||||
|
||||
LOG.info(_LI("Delete plan with id: %s"), id, context=context)
|
||||
|
||||
# TODO(chenying)
|
||||
return webob.Response(status_int=202)
|
||||
try:
|
||||
plan = self._plan_get(context, id)
|
||||
except exception.PlanNotFound as error:
|
||||
raise exc.HTTPNotFound(explanation=error.msg)
|
||||
|
||||
check_policy(context, 'delete', plan)
|
||||
plan.destroy()
|
||||
LOG.info(_LI("Delete plan request issued successfully."),
|
||||
resource={'id': plan.id})
|
||||
|
||||
def index(self, req):
|
||||
"""Returns a summary list of plans."""
|
||||
"""Returns a list of plans, transformed through view builder."""
|
||||
context = req.environ['smaug.context']
|
||||
|
||||
# TODO(chenying)
|
||||
LOG.info(_LI("Show plan list"), context=context)
|
||||
|
||||
return {'plan': "Plans index test."}
|
||||
params = req.params.copy()
|
||||
marker, limit, offset = common.get_pagination_params(params)
|
||||
sort_keys, sort_dirs = common.get_sort_params(params)
|
||||
filters = params
|
||||
|
||||
def detail(self, req):
|
||||
"""Returns a detailed list of plans."""
|
||||
utils.remove_invalid_filter_options(context,
|
||||
filters,
|
||||
self._get_plan_filter_options())
|
||||
|
||||
# TODO(chenying)
|
||||
utils.check_filters(filters)
|
||||
plans = self._get_all(context, marker, limit,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
filters=filters,
|
||||
offset=offset)
|
||||
|
||||
return {'plan': "Plans detail test."}
|
||||
retval_plans = self._view_builder.detail_list(req, plans)
|
||||
|
||||
LOG.info(_LI("Show plan list request issued successfully."))
|
||||
|
||||
return retval_plans
|
||||
|
||||
def _get_all(self, context, marker=None, limit=None, sort_keys=None,
|
||||
sort_dirs=None, filters=None, offset=None):
|
||||
check_policy(context, 'get_all')
|
||||
|
||||
if filters is None:
|
||||
filters = {}
|
||||
|
||||
all_tenants = utils.get_bool_param('all_tenants', filters)
|
||||
|
||||
try:
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
if limit < 0:
|
||||
msg = _('limit param must be positive')
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
except ValueError:
|
||||
msg = _('limit param must be an integer')
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
if filters:
|
||||
LOG.debug("Searching by: %s.", six.text_type(filters))
|
||||
|
||||
if context.is_admin and all_tenants:
|
||||
# Need to remove all_tenants to pass the filtering below.
|
||||
del filters['all_tenants']
|
||||
plans = objects.PlanList.get_all(context, marker, limit,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
filters=filters,
|
||||
offset=offset)
|
||||
else:
|
||||
plans = objects.PlanList.get_all_by_project(
|
||||
context, context.project_id, marker, limit,
|
||||
sort_keys=sort_keys, sort_dirs=sort_dirs, filters=filters,
|
||||
offset=offset)
|
||||
|
||||
LOG.info(_LI("Get all plans completed successfully."))
|
||||
return plans
|
||||
|
||||
def _get_plan_filter_options(self):
|
||||
"""Return plan search options allowed by non-admin."""
|
||||
return CONF.query_plan_filters
|
||||
|
||||
def create(self, req, body):
|
||||
"""Creates a new plan."""
|
||||
@ -153,6 +275,10 @@ class PlansController(wsgi.Controller):
|
||||
msg = _("Missing required element '%s' in request body") % 'plan'
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if not uuidutils.is_uuid_like(id):
|
||||
msg = _("Invalid plan id provided.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
plan = body['plan']
|
||||
update_dict = {}
|
||||
|
||||
@ -186,6 +312,10 @@ class PlansController(wsgi.Controller):
|
||||
return retval
|
||||
|
||||
def _plan_get(self, context, plan_id):
|
||||
if not uuidutils.is_uuid_like(plan_id):
|
||||
msg = _("Invalid plan id provided.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
plan = objects.Plan.get_by_id(context, plan_id)
|
||||
try:
|
||||
check_policy(context, 'get', plan)
|
||||
|
@ -26,7 +26,7 @@ class APIRouter(wsgi_common.Router):
|
||||
scheduled_operation_resources = scheduled_operations.create_resource()
|
||||
mapper.resource("plan", "plans",
|
||||
controller=plans_resources,
|
||||
collection={'detail': 'GET'},
|
||||
collection={},
|
||||
member={'action': 'POST'})
|
||||
mapper.resource("scheduled_operation", "scheduled_operations",
|
||||
controller=scheduled_operation_resources,
|
||||
|
@ -391,3 +391,14 @@ def plan_get_all(context, marker, limit, sort_keys=None, sort_dirs=None,
|
||||
return IMPL.plan_get_all(context, marker, limit, sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs, filters=filters,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def plan_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None, filters=None,
|
||||
offset=None):
|
||||
"""Get all plans belonging to a project."""
|
||||
return IMPL.plan_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
filters=filters,
|
||||
offset=offset)
|
||||
|
@ -24,9 +24,12 @@ from oslo_db import api as oslo_db_api
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_db import options
|
||||
from oslo_db.sqlalchemy import session as db_session
|
||||
from oslo_db.sqlalchemy import utils as sqlalchemyutils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm import RelationshipProperty
|
||||
from sqlalchemy.sql.expression import literal_column
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
@ -683,3 +686,265 @@ def plan_resources_update(context, plan_id, resources):
|
||||
return _plan_resources_update(context,
|
||||
plan_id,
|
||||
resources)
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def plan_get_all(context, marker, limit, sort_keys=None, sort_dirs=None,
|
||||
filters=None, offset=None):
|
||||
"""Retrieves all plans.
|
||||
|
||||
If no sort parameters are specified then the returned plans are sorted
|
||||
first by the 'created_at' key and then by the 'id' key in descending
|
||||
order.
|
||||
|
||||
:param context: context to query under
|
||||
:param marker: the last item of the previous page, used to determine the
|
||||
next page of results to return
|
||||
:param limit: maximum number of items to return
|
||||
:param sort_keys: list of attributes by which results should be sorted,
|
||||
paired with corresponding item in sort_dirs
|
||||
:param sort_dirs: list of directions in which results should be sorted,
|
||||
paired with corresponding item in sort_keys
|
||||
:param filters: dictionary of filters; values that are in lists, tuples,
|
||||
or sets cause an 'IN' operation, while exact matching
|
||||
is used for other values, see _process_plan_filters
|
||||
function for more information
|
||||
:returns: list of matching plans
|
||||
"""
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
# Generate the query
|
||||
query = _generate_paginate_query(context, session, marker, limit,
|
||||
sort_keys, sort_dirs, filters, offset)
|
||||
# No plans would match, return empty list
|
||||
if query is None:
|
||||
return []
|
||||
return query.all()
|
||||
|
||||
|
||||
@require_context
|
||||
def plan_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None, filters=None,
|
||||
offset=None):
|
||||
"""Retrieves all plans in a project.
|
||||
|
||||
If no sort parameters are specified then the returned plans are sorted
|
||||
first by the 'created_at' key and then by the 'id' key in descending
|
||||
order.
|
||||
|
||||
:param context: context to query under
|
||||
:param project_id: project for all plans being retrieved
|
||||
:param marker: the last item of the previous page, used to determine the
|
||||
next page of results to return
|
||||
:param limit: maximum number of items to return
|
||||
:param sort_keys: list of attributes by which results should be sorted,
|
||||
paired with corresponding item in sort_dirs
|
||||
:param sort_dirs: list of directions in which results should be sorted,
|
||||
paired with corresponding item in sort_keys
|
||||
:param filters: dictionary of filters; values that are in lists, tuples,
|
||||
or sets cause an 'IN' operation, while exact matching
|
||||
is used for other values, see _process_plan_filters
|
||||
function for more information
|
||||
:returns: list of matching plans
|
||||
"""
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
authorize_project_context(context, project_id)
|
||||
# Add in the project filter without modifying the given filters
|
||||
filters = filters.copy() if filters else {}
|
||||
filters['project_id'] = project_id
|
||||
# Generate the query
|
||||
query = _generate_paginate_query(context, session, marker, limit,
|
||||
sort_keys, sort_dirs, filters, offset)
|
||||
# No plans would match, return empty list
|
||||
if query is None:
|
||||
return []
|
||||
return query.all()
|
||||
|
||||
|
||||
def _process_plan_filters(query, filters):
|
||||
"""Common filter processing for Plan queries.
|
||||
|
||||
Filter values that are in lists, tuples, or sets cause an 'IN' operator
|
||||
to be used, while exact matching ('==' operator) is used for other values.
|
||||
|
||||
A 'metadata' filter key must correspond to a dictionary value of metadata
|
||||
key-value pairs.
|
||||
|
||||
:param query: Model query to use
|
||||
:param filters: dictionary of filters
|
||||
:returns: updated query or None
|
||||
"""
|
||||
filters = filters.copy()
|
||||
|
||||
# Apply exact match filters for everything else, ensure that the
|
||||
# filter value exists on the model
|
||||
for key in filters.keys():
|
||||
# metadata is unique, must be a dict
|
||||
if key == 'resources':
|
||||
if not isinstance(filters[key], dict):
|
||||
LOG.debug("'metadata' filter value is not valid.")
|
||||
return None
|
||||
continue
|
||||
try:
|
||||
column_attr = getattr(models.Plan, key)
|
||||
# Do not allow relationship properties since those require
|
||||
# schema specific knowledge
|
||||
prop = getattr(column_attr, 'property')
|
||||
if isinstance(prop, RelationshipProperty):
|
||||
LOG.debug(("'%s' filter key is not valid, "
|
||||
"it maps to a relationship."), key)
|
||||
return None
|
||||
except AttributeError:
|
||||
LOG.debug("'%s' filter key is not valid.", key)
|
||||
return None
|
||||
|
||||
# Holds the simple exact matches
|
||||
filter_dict = {}
|
||||
|
||||
# Iterate over all filters, special case the filter if necessary
|
||||
for key, value in filters.items():
|
||||
if key == 'resources':
|
||||
col_attr = getattr(models.Plan, 'continue')
|
||||
for k, v in value.items():
|
||||
query = query.filter(or_(col_attr.any(key=k, value=v)))
|
||||
elif isinstance(value, (list, tuple, set, frozenset)):
|
||||
# Looking for values in a list; apply to query directly
|
||||
column_attr = getattr(models.Plan, key)
|
||||
query = query.filter(column_attr.in_(value))
|
||||
else:
|
||||
# OK, simple exact match; save for later
|
||||
filter_dict[key] = value
|
||||
|
||||
# Apply simple exact matches
|
||||
if filter_dict:
|
||||
query = query.filter_by(**filter_dict)
|
||||
return query
|
||||
|
||||
|
||||
###############################
|
||||
|
||||
|
||||
PAGINATION_HELPERS = {
|
||||
models.Plan: (_plan_get_query, _process_plan_filters, _plan_get)
|
||||
}
|
||||
|
||||
|
||||
###############################
|
||||
|
||||
def _generate_paginate_query(context, session, marker, limit, sort_keys,
|
||||
sort_dirs, filters, offset=None,
|
||||
paginate_type=models.Plan):
|
||||
"""Generate the query to include the filters and the paginate options.
|
||||
|
||||
Returns a query with sorting / pagination criteria added or None
|
||||
if the given filters will not yield any results.
|
||||
|
||||
:param context: context to query under
|
||||
:param session: the session to use
|
||||
:param marker: the last item of the previous page; we returns the next
|
||||
results after this value.
|
||||
:param limit: maximum number of items to return
|
||||
:param sort_keys: list of attributes by which results should be sorted,
|
||||
paired with corresponding item in sort_dirs
|
||||
:param sort_dirs: list of directions in which results should be sorted,
|
||||
paired with corresponding item in sort_keys
|
||||
:param filters: dictionary of filters; values that are in lists, tuples,
|
||||
or sets cause an 'IN' operation, while exact matching
|
||||
is used for other values, see _process_plan_filters
|
||||
function for more information
|
||||
:param offset: number of items to skip
|
||||
:param paginate_type: type of pagination to generate
|
||||
:returns: updated query or None
|
||||
"""
|
||||
get_query, process_filters, get = PAGINATION_HELPERS[paginate_type]
|
||||
|
||||
sort_keys, sort_dirs = process_sort_params(sort_keys,
|
||||
sort_dirs,
|
||||
default_dir='desc')
|
||||
query = get_query(context, session=session)
|
||||
|
||||
if filters:
|
||||
query = process_filters(query, filters)
|
||||
if query is None:
|
||||
return None
|
||||
|
||||
marker_object = None
|
||||
if marker is not None:
|
||||
marker_object = get(context, marker, session)
|
||||
|
||||
return sqlalchemyutils.paginate_query(query, paginate_type, limit,
|
||||
sort_keys,
|
||||
marker=marker_object,
|
||||
sort_dirs=sort_dirs)
|
||||
|
||||
|
||||
def process_sort_params(sort_keys, sort_dirs, default_keys=None,
|
||||
default_dir='asc'):
|
||||
"""Process the sort parameters to include default keys.
|
||||
|
||||
Creates a list of sort keys and a list of sort directions. Adds the default
|
||||
keys to the end of the list if they are not already included.
|
||||
|
||||
When adding the default keys to the sort keys list, the associated
|
||||
direction is:
|
||||
1) The first element in the 'sort_dirs' list (if specified), else
|
||||
2) 'default_dir' value (Note that 'asc' is the default value since this is
|
||||
the default in sqlalchemy.utils.paginate_query)
|
||||
|
||||
:param sort_keys: List of sort keys to include in the processed list
|
||||
:param sort_dirs: List of sort directions to include in the processed list
|
||||
:param default_keys: List of sort keys that need to be included in the
|
||||
processed list, they are added at the end of the list
|
||||
if not already specified.
|
||||
:param default_dir: Sort direction associated with each of the default
|
||||
keys that are not supplied, used when they are added
|
||||
to the processed list
|
||||
:returns: list of sort keys, list of sort directions
|
||||
:raise exception.InvalidInput: If more sort directions than sort keys
|
||||
are specified or if an invalid sort
|
||||
direction is specified
|
||||
"""
|
||||
if default_keys is None:
|
||||
default_keys = ['created_at', 'id']
|
||||
|
||||
# Determine direction to use for when adding default keys
|
||||
if sort_dirs and len(sort_dirs):
|
||||
default_dir_value = sort_dirs[0]
|
||||
else:
|
||||
default_dir_value = default_dir
|
||||
|
||||
# Create list of keys (do not modify the input list)
|
||||
if sort_keys:
|
||||
result_keys = list(sort_keys)
|
||||
else:
|
||||
result_keys = []
|
||||
|
||||
# If a list of directions is not provided, use the default sort direction
|
||||
# for all provided keys.
|
||||
if sort_dirs:
|
||||
result_dirs = []
|
||||
# Verify sort direction
|
||||
for sort_dir in sort_dirs:
|
||||
if sort_dir not in ('asc', 'desc'):
|
||||
msg = _("Unknown sort direction, must be 'desc' or 'asc'.")
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
result_dirs.append(sort_dir)
|
||||
else:
|
||||
result_dirs = [default_dir_value for _sort_key in result_keys]
|
||||
|
||||
# Ensure that the key and direction length match
|
||||
while len(result_dirs) < len(result_keys):
|
||||
result_dirs.append(default_dir_value)
|
||||
# Unless more direction are specified, which is an error
|
||||
if len(result_dirs) > len(result_keys):
|
||||
msg = _("Sort direction array size exceeds sort key array size.")
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
# Ensure defaults are included
|
||||
for key in default_keys:
|
||||
if key not in result_keys:
|
||||
result_keys.append(key)
|
||||
result_dirs.append(default_dir_value)
|
||||
|
||||
return result_keys, result_dirs
|
||||
|
@ -146,6 +146,10 @@ class Invalid(SmaugException):
|
||||
code = 400
|
||||
|
||||
|
||||
class InvalidParameterValue(Invalid):
|
||||
message = _("%(err)s")
|
||||
|
||||
|
||||
class InvalidInput(Invalid):
|
||||
message = _("Invalid input received: %(reason)s")
|
||||
|
||||
|
@ -152,3 +152,15 @@ class PlanList(base.ObjectListBase, base.SmaugObject):
|
||||
expected_attrs = ['resources']
|
||||
return base.obj_make_list(context, cls(context), objects.Plan,
|
||||
plans, expected_attrs=expected_attrs)
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_all_by_project(cls, context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None, filters=None,
|
||||
offset=None):
|
||||
plans = db.plan_get_all_by_project(context, project_id, marker,
|
||||
limit, sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
filters=filters, offset=offset)
|
||||
expected_attrs = ['resources']
|
||||
return base.obj_make_list(context, cls(context), objects.Plan,
|
||||
plans, expected_attrs=expected_attrs)
|
||||
|
@ -25,7 +25,7 @@ CONF = cfg.CONF
|
||||
|
||||
DEFAULT_NAME = 'My 3 tier application'
|
||||
DEFAULT_PROVIDER_ID = 'efc6a88b-9096-4bb6-8634-cda182a6e12a'
|
||||
DEFAULT_STATUS = 'started'
|
||||
DEFAULT_STATUS = 'suspended'
|
||||
DEFAULT_PROJECT_ID = '39bb894794b741e982bd26144d2949f6'
|
||||
DEFAULT_RESOURCES = [{'id': 'key1',
|
||||
"type": "value1"}]
|
||||
@ -92,15 +92,17 @@ class PlanApiTest(base.TestCase):
|
||||
plan = self._plan_in_request_body()
|
||||
body = {"planxx": plan}
|
||||
req = fakes.HTTPRequest.blank('/v1/plans')
|
||||
self.assertRaises(exc.HTTPBadRequest, self.controller.update,
|
||||
req, "1", body)
|
||||
self.assertRaises(
|
||||
exc.HTTPBadRequest, self.controller.update,
|
||||
req, "2a9ce1f3-cc1a-4516-9435-0ebb13caa398", body)
|
||||
|
||||
def test_plan_update_InvalidId(self):
|
||||
plan = self._plan_in_request_body()
|
||||
body = {"plan": plan}
|
||||
req = fakes.HTTPRequest.blank('/v1/plans')
|
||||
self.assertRaises(exc.HTTPNotFound, self.controller.update,
|
||||
req, "1", body)
|
||||
self.assertRaises(
|
||||
exc.HTTPNotFound, self.controller.update,
|
||||
req, "2a9ce1f3-cc1a-4516-9435-0ebb13caa398", body)
|
||||
|
||||
def test_plan_update_InvalidResources(self):
|
||||
plan = self._plan_in_request_body(name=DEFAULT_NAME,
|
||||
@ -110,15 +112,55 @@ class PlanApiTest(base.TestCase):
|
||||
resources=[{'key1': 'value1'}])
|
||||
body = {"plan": plan}
|
||||
req = fakes.HTTPRequest.blank('/v1/plans')
|
||||
self.assertRaises(exception.InvalidInput, self.controller.update,
|
||||
req, "1", body)
|
||||
self.assertRaises(
|
||||
exception.InvalidInput, self.controller.update,
|
||||
req, "2a9ce1f3-cc1a-4516-9435-0ebb13caa398", body)
|
||||
|
||||
@mock.patch(
|
||||
'smaug.api.v1.plans.PlansController._get_all')
|
||||
def test_plan_list_detail(self, moak_get_all):
|
||||
req = fakes.HTTPRequest.blank('/v1/plans')
|
||||
self.controller.index(req)
|
||||
self.assertTrue(moak_get_all.called)
|
||||
|
||||
@mock.patch(
|
||||
'smaug.api.v1.plans.PlansController._plan_get')
|
||||
def test_plan_show(self, moak_plan_get):
|
||||
req = fakes.HTTPRequest.blank('/v1/plans')
|
||||
self.controller.\
|
||||
show(req, '2a9ce1f3-cc1a-4516-9435-0ebb13caa398')
|
||||
self.assertTrue(moak_plan_get.called)
|
||||
|
||||
def test_plan_show_Invalid(self):
|
||||
req = fakes.HTTPRequest.blank('/v1/plans/1')
|
||||
self.assertRaises(
|
||||
exc.HTTPBadRequest, self.controller.show,
|
||||
req, "1")
|
||||
|
||||
@mock.patch(
|
||||
'smaug.api.v1.plans.PlansController._plan_get')
|
||||
def test_plan_delete(self, moak_plan_get):
|
||||
req = fakes.HTTPRequest.blank('/v1/plans')
|
||||
self.controller.\
|
||||
show(req, '2a9ce1f3-cc1a-4516-9435-0ebb13caa398')
|
||||
self.assertTrue(moak_plan_get.called)
|
||||
|
||||
def test_plan_delete_Invalid(self):
|
||||
req = fakes.HTTPRequest.blank('/v1/plans/1')
|
||||
self.assertRaises(
|
||||
exc.HTTPBadRequest, self.controller.show,
|
||||
req, "1")
|
||||
|
||||
@mock.patch(
|
||||
'smaug.api.v1.plans.check_policy')
|
||||
@mock.patch(
|
||||
'smaug.api.v1.plans.PlansController._plan_get')
|
||||
def test_plan_update_InvalidStatus(self, mock_plan_get, mock_check_policy):
|
||||
plan = self._plan_in_request_body()
|
||||
plan = self._plan_in_request_body(name=DEFAULT_NAME,
|
||||
provider_id=DEFAULT_PROVIDER_ID,
|
||||
status="started",
|
||||
project_id=DEFAULT_PROJECT_ID,
|
||||
resources=DEFAULT_RESOURCES)
|
||||
body = {"plan": plan}
|
||||
req = fakes.HTTPRequest.blank('/v1/plans')
|
||||
mock_plan_get.return_value = plan
|
||||
|
@ -28,10 +28,3 @@ class PlansRouterTestCase(base.TestCase):
|
||||
req.content_type = 'application/json'
|
||||
response = req.get_response(self.app)
|
||||
self.assertEqual(200, response.status_int)
|
||||
|
||||
def test_plans_detail(self):
|
||||
req = fakes.HTTPRequest.blank('/fakeproject/plans/detail')
|
||||
req.method = 'GET'
|
||||
req.content_type = 'application/json'
|
||||
response = req.get_response(self.app)
|
||||
self.assertEqual(200, response.status_int)
|
||||
|
@ -11,11 +11,14 @@
|
||||
# under the License.
|
||||
|
||||
"""Utilities and helper functions."""
|
||||
import ast
|
||||
import os
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import timeutils
|
||||
|
||||
import six
|
||||
|
||||
from smaug import exception
|
||||
@ -78,3 +81,43 @@ def service_is_up(service):
|
||||
elapsed = (timeutils.utcnow(with_timezone=True) -
|
||||
last_heartbeat).total_seconds()
|
||||
return abs(elapsed) <= CONF.service_down_time
|
||||
|
||||
|
||||
def remove_invalid_filter_options(context, filters,
|
||||
allowed_search_options):
|
||||
"""Remove search options that are not valid for non-admin API/context."""
|
||||
|
||||
if context.is_admin:
|
||||
# Allow all options
|
||||
return
|
||||
# Otherwise, strip out all unknown options
|
||||
unknown_options = [opt for opt in filters
|
||||
if opt not in allowed_search_options]
|
||||
bad_options = ", ".join(unknown_options)
|
||||
LOG.debug("Removing options '%s' from query.", bad_options)
|
||||
for opt in unknown_options:
|
||||
del filters[opt]
|
||||
|
||||
|
||||
def check_filters(filters):
|
||||
for k, v in six.iteritems(filters):
|
||||
try:
|
||||
filters[k] = ast.literal_eval(v)
|
||||
except (ValueError, SyntaxError):
|
||||
LOG.debug('Could not evaluate value %s, assuming string', v)
|
||||
|
||||
|
||||
def is_valid_boolstr(val):
|
||||
"""Check if the provided string is a valid bool string or not."""
|
||||
val = str(val).lower()
|
||||
return val in ('true', 'false', 'yes', 'no', 'y', 'n', '1', '0')
|
||||
|
||||
|
||||
def get_bool_param(param_string, params):
|
||||
param = params.get(param_string, False)
|
||||
if not is_valid_boolstr(param):
|
||||
msg = _('Value %(param)s for %(param_string)s is not a '
|
||||
'boolean.') % {'param': param, 'param_string': param_string}
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
return strutils.bool_from_string(param, strict=True)
|
||||
|
Loading…
Reference in New Issue
Block a user