add parent/sub-resource support into Quantum API framework
- quantum.api.v2.base.Controller class now able to handle sub-resources - quantum.api.v2.router.APIRouter now able to specify sub-resources Fixes bug 1085968 Change-Id: I07f2c1f3d974f7f17d4947804bde064dd8004a84
This commit is contained in:
parent
8e7fe17caf
commit
f4e1fa0ea7
@ -101,9 +101,14 @@ def _filters(request, attr_info):
|
|||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
|
LIST = 'list'
|
||||||
|
SHOW = 'show'
|
||||||
|
CREATE = 'create'
|
||||||
|
UPDATE = 'update'
|
||||||
|
DELETE = 'delete'
|
||||||
|
|
||||||
def __init__(self, plugin, collection, resource, attr_info,
|
def __init__(self, plugin, collection, resource, attr_info,
|
||||||
allow_bulk=False, member_actions=None):
|
allow_bulk=False, member_actions=None, parent=None):
|
||||||
if member_actions is None:
|
if member_actions is None:
|
||||||
member_actions = []
|
member_actions = []
|
||||||
self._plugin = plugin
|
self._plugin = plugin
|
||||||
@ -117,6 +122,20 @@ class Controller(object):
|
|||||||
self._publisher_id = notifier_api.publisher_id('network')
|
self._publisher_id = notifier_api.publisher_id('network')
|
||||||
self._member_actions = member_actions
|
self._member_actions = member_actions
|
||||||
|
|
||||||
|
if parent:
|
||||||
|
self._parent_id_name = '%s_id' % parent['member_name']
|
||||||
|
parent_part = '_%s' % parent['member_name']
|
||||||
|
else:
|
||||||
|
self._parent_id_name = None
|
||||||
|
parent_part = ''
|
||||||
|
self._plugin_handlers = {
|
||||||
|
self.LIST: 'get%s_%s' % (parent_part, self._collection),
|
||||||
|
self.SHOW: 'get%s_%s' % (parent_part, self._resource)
|
||||||
|
}
|
||||||
|
for action in [self.CREATE, self.UPDATE, self.DELETE]:
|
||||||
|
self._plugin_handlers[action] = '%s%s_%s' % (action, parent_part,
|
||||||
|
self._resource)
|
||||||
|
|
||||||
def _is_native_bulk_supported(self):
|
def _is_native_bulk_supported(self):
|
||||||
native_bulk_attr_name = ("_%s__native_bulk_support"
|
native_bulk_attr_name = ("_%s__native_bulk_support"
|
||||||
% self._plugin.__class__.__name__)
|
% self._plugin.__class__.__name__)
|
||||||
@ -152,7 +171,7 @@ class Controller(object):
|
|||||||
else:
|
else:
|
||||||
raise AttributeError
|
raise AttributeError
|
||||||
|
|
||||||
def _items(self, request, do_authz=False):
|
def _items(self, request, do_authz=False, parent_id=None):
|
||||||
"""Retrieves and formats a list of elements of the requested entity"""
|
"""Retrieves and formats a list of elements of the requested entity"""
|
||||||
# NOTE(salvatore-orlando): The following ensures that fields which
|
# NOTE(salvatore-orlando): The following ensures that fields which
|
||||||
# are needed for authZ policy validation are not stripped away by the
|
# are needed for authZ policy validation are not stripped away by the
|
||||||
@ -160,7 +179,9 @@ class Controller(object):
|
|||||||
original_fields, fields_to_add = self._do_field_list(_fields(request))
|
original_fields, fields_to_add = self._do_field_list(_fields(request))
|
||||||
kwargs = {'filters': _filters(request, self._attr_info),
|
kwargs = {'filters': _filters(request, self._attr_info),
|
||||||
'fields': original_fields}
|
'fields': original_fields}
|
||||||
obj_getter = getattr(self._plugin, "get_%s" % self._collection)
|
if parent_id:
|
||||||
|
kwargs[self._parent_id_name] = parent_id
|
||||||
|
obj_getter = getattr(self._plugin, self._plugin_handlers[self.LIST])
|
||||||
obj_list = obj_getter(request.context, **kwargs)
|
obj_list = obj_getter(request.context, **kwargs)
|
||||||
# Check authz
|
# Check authz
|
||||||
if do_authz:
|
if do_authz:
|
||||||
@ -169,17 +190,20 @@ class Controller(object):
|
|||||||
# Omit items from list that should not be visible
|
# Omit items from list that should not be visible
|
||||||
obj_list = [obj for obj in obj_list
|
obj_list = [obj for obj in obj_list
|
||||||
if policy.check(request.context,
|
if policy.check(request.context,
|
||||||
"get_%s" % self._resource,
|
self._plugin_handlers[self.SHOW],
|
||||||
obj,
|
obj,
|
||||||
plugin=self._plugin)]
|
plugin=self._plugin)]
|
||||||
return {self._collection: [self._view(obj,
|
return {self._collection: [self._view(obj,
|
||||||
fields_to_strip=fields_to_add)
|
fields_to_strip=fields_to_add)
|
||||||
for obj in obj_list]}
|
for obj in obj_list]}
|
||||||
|
|
||||||
def _item(self, request, id, do_authz=False, field_list=None):
|
def _item(self, request, id, do_authz=False, field_list=None,
|
||||||
|
parent_id=None):
|
||||||
"""Retrieves and formats a single element of the requested entity"""
|
"""Retrieves and formats a single element of the requested entity"""
|
||||||
kwargs = {'fields': field_list}
|
kwargs = {'fields': field_list}
|
||||||
action = "get_%s" % self._resource
|
action = self._plugin_handlers[self.SHOW]
|
||||||
|
if parent_id:
|
||||||
|
kwargs[self._parent_id_name] = parent_id
|
||||||
obj_getter = getattr(self._plugin, action)
|
obj_getter = getattr(self._plugin, action)
|
||||||
obj = obj_getter(request.context, id, **kwargs)
|
obj = obj_getter(request.context, id, **kwargs)
|
||||||
# Check authz
|
# Check authz
|
||||||
@ -189,33 +213,38 @@ class Controller(object):
|
|||||||
policy.enforce(request.context, action, obj, plugin=self._plugin)
|
policy.enforce(request.context, action, obj, plugin=self._plugin)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def index(self, request):
|
def index(self, request, **kwargs):
|
||||||
"""Returns a list of the requested entity"""
|
"""Returns a list of the requested entity"""
|
||||||
return self._items(request, True)
|
parent_id = kwargs.get(self._parent_id_name)
|
||||||
|
return self._items(request, True, parent_id)
|
||||||
|
|
||||||
def show(self, request, id):
|
def show(self, request, id, **kwargs):
|
||||||
"""Returns detailed information about the requested entity"""
|
"""Returns detailed information about the requested entity"""
|
||||||
try:
|
try:
|
||||||
# NOTE(salvatore-orlando): The following ensures that fields
|
# NOTE(salvatore-orlando): The following ensures that fields
|
||||||
# which are needed for authZ policy validation are not stripped
|
# which are needed for authZ policy validation are not stripped
|
||||||
# away by the plugin before returning.
|
# away by the plugin before returning.
|
||||||
field_list, added_fields = self._do_field_list(_fields(request))
|
field_list, added_fields = self._do_field_list(_fields(request))
|
||||||
|
parent_id = kwargs.get(self._parent_id_name)
|
||||||
return {self._resource:
|
return {self._resource:
|
||||||
self._view(self._item(request,
|
self._view(self._item(request,
|
||||||
id,
|
id,
|
||||||
do_authz=True,
|
do_authz=True,
|
||||||
field_list=field_list),
|
field_list=field_list,
|
||||||
|
parent_id=parent_id),
|
||||||
fields_to_strip=added_fields)}
|
fields_to_strip=added_fields)}
|
||||||
except exceptions.PolicyNotAuthorized:
|
except exceptions.PolicyNotAuthorized:
|
||||||
# To avoid giving away information, pretend that it
|
# To avoid giving away information, pretend that it
|
||||||
# doesn't exist
|
# doesn't exist
|
||||||
raise webob.exc.HTTPNotFound()
|
raise webob.exc.HTTPNotFound()
|
||||||
|
|
||||||
def _emulate_bulk_create(self, obj_creator, request, body):
|
def _emulate_bulk_create(self, obj_creator, request, body, parent_id=None):
|
||||||
objs = []
|
objs = []
|
||||||
try:
|
try:
|
||||||
for item in body[self._collection]:
|
for item in body[self._collection]:
|
||||||
kwargs = {self._resource: item}
|
kwargs = {self._resource: item}
|
||||||
|
if parent_id:
|
||||||
|
kwargs[self._parent_id_name] = parent_id
|
||||||
objs.append(self._view(obj_creator(request.context,
|
objs.append(self._view(obj_creator(request.context,
|
||||||
**kwargs)))
|
**kwargs)))
|
||||||
return objs
|
return objs
|
||||||
@ -223,10 +252,12 @@ class Controller(object):
|
|||||||
# could raise any kind of exception
|
# could raise any kind of exception
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
delete_action = "delete_%s" % self._resource
|
obj_deleter = getattr(self._plugin,
|
||||||
obj_deleter = getattr(self._plugin, delete_action)
|
self._plugin_handlers[self.DELETE])
|
||||||
try:
|
try:
|
||||||
obj_deleter(request.context, obj['id'])
|
kwargs = ({self._parent_id_name: parent_id} if parent_id
|
||||||
|
else {})
|
||||||
|
obj_deleter(request.context, obj['id'], **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
# broad catch as our only purpose is to log the exception
|
# broad catch as our only purpose is to log the exception
|
||||||
LOG.exception(_("Unable to undo add for "
|
LOG.exception(_("Unable to undo add for "
|
||||||
@ -239,8 +270,9 @@ class Controller(object):
|
|||||||
# it is then deleted
|
# it is then deleted
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def create(self, request, body=None):
|
def create(self, request, body=None, **kwargs):
|
||||||
"""Creates a new instance of the requested entity"""
|
"""Creates a new instance of the requested entity"""
|
||||||
|
parent_id = kwargs.get(self._parent_id_name)
|
||||||
notifier_api.notify(request.context,
|
notifier_api.notify(request.context,
|
||||||
self._publisher_id,
|
self._publisher_id,
|
||||||
self._resource + '.create.start',
|
self._resource + '.create.start',
|
||||||
@ -249,7 +281,7 @@ class Controller(object):
|
|||||||
body = Controller.prepare_request_body(request.context, body, True,
|
body = Controller.prepare_request_body(request.context, body, True,
|
||||||
self._resource, self._attr_info,
|
self._resource, self._attr_info,
|
||||||
allow_bulk=self._allow_bulk)
|
allow_bulk=self._allow_bulk)
|
||||||
action = "create_%s" % self._resource
|
action = self._plugin_handlers[self.CREATE]
|
||||||
# Check authz
|
# Check authz
|
||||||
try:
|
try:
|
||||||
if self._collection in body:
|
if self._collection in body:
|
||||||
@ -312,34 +344,37 @@ class Controller(object):
|
|||||||
create_result)
|
create_result)
|
||||||
return create_result
|
return create_result
|
||||||
|
|
||||||
|
kwargs = {self._parent_id_name: parent_id} if parent_id else {}
|
||||||
if self._collection in body and self._native_bulk:
|
if self._collection in body and self._native_bulk:
|
||||||
# plugin does atomic bulk create operations
|
# plugin does atomic bulk create operations
|
||||||
obj_creator = getattr(self._plugin, "%s_bulk" % action)
|
obj_creator = getattr(self._plugin, "%s_bulk" % action)
|
||||||
objs = obj_creator(request.context, body)
|
objs = obj_creator(request.context, body, **kwargs)
|
||||||
return notify({self._collection: [self._view(obj)
|
return notify({self._collection: [self._view(obj)
|
||||||
for obj in objs]})
|
for obj in objs]})
|
||||||
else:
|
else:
|
||||||
obj_creator = getattr(self._plugin, action)
|
obj_creator = getattr(self._plugin, action)
|
||||||
if self._collection in body:
|
if self._collection in body:
|
||||||
# Emulate atomic bulk behavior
|
# Emulate atomic bulk behavior
|
||||||
objs = self._emulate_bulk_create(obj_creator, request, body)
|
objs = self._emulate_bulk_create(obj_creator, request,
|
||||||
|
body, parent_id)
|
||||||
return notify({self._collection: objs})
|
return notify({self._collection: objs})
|
||||||
else:
|
else:
|
||||||
kwargs = {self._resource: body}
|
kwargs.update({self._resource: body})
|
||||||
obj = obj_creator(request.context, **kwargs)
|
obj = obj_creator(request.context, **kwargs)
|
||||||
return notify({self._resource: self._view(obj)})
|
return notify({self._resource: self._view(obj)})
|
||||||
|
|
||||||
def delete(self, request, id):
|
def delete(self, request, id, **kwargs):
|
||||||
"""Deletes the specified entity"""
|
"""Deletes the specified entity"""
|
||||||
notifier_api.notify(request.context,
|
notifier_api.notify(request.context,
|
||||||
self._publisher_id,
|
self._publisher_id,
|
||||||
self._resource + '.delete.start',
|
self._resource + '.delete.start',
|
||||||
notifier_api.INFO,
|
notifier_api.INFO,
|
||||||
{self._resource + '_id': id})
|
{self._resource + '_id': id})
|
||||||
action = "delete_%s" % self._resource
|
action = self._plugin_handlers[self.DELETE]
|
||||||
|
|
||||||
# Check authz
|
# Check authz
|
||||||
obj = self._item(request, id)
|
parent_id = kwargs.get(self._parent_id_name)
|
||||||
|
obj = self._item(request, id, parent_id=parent_id)
|
||||||
try:
|
try:
|
||||||
policy.enforce(request.context,
|
policy.enforce(request.context,
|
||||||
action,
|
action,
|
||||||
@ -351,15 +386,16 @@ class Controller(object):
|
|||||||
raise webob.exc.HTTPNotFound()
|
raise webob.exc.HTTPNotFound()
|
||||||
|
|
||||||
obj_deleter = getattr(self._plugin, action)
|
obj_deleter = getattr(self._plugin, action)
|
||||||
obj_deleter(request.context, id)
|
obj_deleter(request.context, id, **kwargs)
|
||||||
notifier_api.notify(request.context,
|
notifier_api.notify(request.context,
|
||||||
self._publisher_id,
|
self._publisher_id,
|
||||||
self._resource + '.delete.end',
|
self._resource + '.delete.end',
|
||||||
notifier_api.INFO,
|
notifier_api.INFO,
|
||||||
{self._resource + '_id': id})
|
{self._resource + '_id': id})
|
||||||
|
|
||||||
def update(self, request, id, body=None):
|
def update(self, request, id, body=None, **kwargs):
|
||||||
"""Updates the specified entity's attributes"""
|
"""Updates the specified entity's attributes"""
|
||||||
|
parent_id = kwargs.get(self._parent_id_name)
|
||||||
payload = body.copy()
|
payload = body.copy()
|
||||||
payload['id'] = id
|
payload['id'] = id
|
||||||
notifier_api.notify(request.context,
|
notifier_api.notify(request.context,
|
||||||
@ -370,7 +406,7 @@ class Controller(object):
|
|||||||
body = Controller.prepare_request_body(request.context, body, False,
|
body = Controller.prepare_request_body(request.context, body, False,
|
||||||
self._resource, self._attr_info,
|
self._resource, self._attr_info,
|
||||||
allow_bulk=self._allow_bulk)
|
allow_bulk=self._allow_bulk)
|
||||||
action = "update_%s" % self._resource
|
action = self._plugin_handlers[self.UPDATE]
|
||||||
# Load object to check authz
|
# Load object to check authz
|
||||||
# but pass only attributes in the original body and required
|
# but pass only attributes in the original body and required
|
||||||
# by the policy engine to the policy 'brain'
|
# by the policy engine to the policy 'brain'
|
||||||
@ -378,7 +414,8 @@ class Controller(object):
|
|||||||
if ('required_by_policy' in value and
|
if ('required_by_policy' in value and
|
||||||
value['required_by_policy'] or
|
value['required_by_policy'] or
|
||||||
not 'default' in value)]
|
not 'default' in value)]
|
||||||
orig_obj = self._item(request, id, field_list=field_list)
|
orig_obj = self._item(request, id, field_list=field_list,
|
||||||
|
parent_id=parent_id)
|
||||||
orig_obj.update(body[self._resource])
|
orig_obj.update(body[self._resource])
|
||||||
try:
|
try:
|
||||||
policy.enforce(request.context,
|
policy.enforce(request.context,
|
||||||
@ -392,6 +429,8 @@ class Controller(object):
|
|||||||
|
|
||||||
obj_updater = getattr(self._plugin, action)
|
obj_updater = getattr(self._plugin, action)
|
||||||
kwargs = {self._resource: body}
|
kwargs = {self._resource: body}
|
||||||
|
if parent_id:
|
||||||
|
kwargs[self._parent_id_name] = parent_id
|
||||||
obj = obj_updater(request.context, id, **kwargs)
|
obj = obj_updater(request.context, id, **kwargs)
|
||||||
result = {self._resource: self._view(obj)}
|
result = {self._resource: self._view(obj)}
|
||||||
notifier_api.notify(request.context,
|
notifier_api.notify(request.context,
|
||||||
@ -526,9 +565,9 @@ class Controller(object):
|
|||||||
|
|
||||||
|
|
||||||
def create_resource(collection, resource, plugin, params, allow_bulk=False,
|
def create_resource(collection, resource, plugin, params, allow_bulk=False,
|
||||||
member_actions=None):
|
member_actions=None, parent=None):
|
||||||
controller = Controller(plugin, collection, resource, params, allow_bulk,
|
controller = Controller(plugin, collection, resource, params, allow_bulk,
|
||||||
member_actions=member_actions)
|
member_actions=member_actions, parent=parent)
|
||||||
|
|
||||||
# NOTE(jkoelker) To anyone wishing to add "proper" xml support
|
# NOTE(jkoelker) To anyone wishing to add "proper" xml support
|
||||||
# this is where you do it
|
# this is where you do it
|
||||||
|
@ -30,6 +30,11 @@ from quantum import wsgi
|
|||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
RESOURCES = {'network': 'networks',
|
||||||
|
'subnet': 'subnets',
|
||||||
|
'port': 'ports'}
|
||||||
|
SUB_RESOURCES = {}
|
||||||
COLLECTION_ACTIONS = ['index', 'create']
|
COLLECTION_ACTIONS = ['index', 'create']
|
||||||
MEMBER_ACTIONS = ['show', 'update', 'delete']
|
MEMBER_ACTIONS = ['show', 'update', 'delete']
|
||||||
REQUIREMENTS = {'id': attributes.UUID_PATTERN, 'format': 'xml|json'}
|
REQUIREMENTS = {'id': attributes.UUID_PATTERN, 'format': 'xml|json'}
|
||||||
@ -75,25 +80,35 @@ class APIRouter(wsgi.Router):
|
|||||||
col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
|
col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
|
||||||
member_actions=MEMBER_ACTIONS)
|
member_actions=MEMBER_ACTIONS)
|
||||||
|
|
||||||
resources = {'network': 'networks',
|
def _map_resource(collection, resource, params, parent=None):
|
||||||
'subnet': 'subnets',
|
|
||||||
'port': 'ports'}
|
|
||||||
|
|
||||||
def _map_resource(collection, resource, params):
|
|
||||||
allow_bulk = cfg.CONF.allow_bulk
|
allow_bulk = cfg.CONF.allow_bulk
|
||||||
controller = base.create_resource(collection, resource,
|
controller = base.create_resource(collection, resource,
|
||||||
plugin, params,
|
plugin, params,
|
||||||
allow_bulk=allow_bulk)
|
allow_bulk=allow_bulk,
|
||||||
|
parent=parent)
|
||||||
|
path_prefix = None
|
||||||
|
if parent:
|
||||||
|
path_prefix = "/%s/{%s_id}/%s" % (parent['collection_name'],
|
||||||
|
parent['member_name'],
|
||||||
|
collection)
|
||||||
mapper_kwargs = dict(controller=controller,
|
mapper_kwargs = dict(controller=controller,
|
||||||
requirements=REQUIREMENTS,
|
requirements=REQUIREMENTS,
|
||||||
|
path_prefix=path_prefix,
|
||||||
**col_kwargs)
|
**col_kwargs)
|
||||||
return mapper.collection(collection, resource,
|
return mapper.collection(collection, resource,
|
||||||
**mapper_kwargs)
|
**mapper_kwargs)
|
||||||
|
|
||||||
mapper.connect('index', '/', controller=Index(resources))
|
mapper.connect('index', '/', controller=Index(RESOURCES))
|
||||||
for resource in resources:
|
for resource in RESOURCES:
|
||||||
_map_resource(resources[resource], resource,
|
_map_resource(RESOURCES[resource], resource,
|
||||||
attributes.RESOURCE_ATTRIBUTE_MAP.get(
|
attributes.RESOURCE_ATTRIBUTE_MAP.get(
|
||||||
resources[resource], dict()))
|
RESOURCES[resource], dict()))
|
||||||
|
|
||||||
|
for resource in SUB_RESOURCES:
|
||||||
|
_map_resource(SUB_RESOURCES[resource]['collection_name'], resource,
|
||||||
|
attributes.RESOURCE_ATTRIBUTE_MAP.get(
|
||||||
|
SUB_RESOURCES[resource]['collection_name'],
|
||||||
|
dict()),
|
||||||
|
SUB_RESOURCES[resource]['parent'])
|
||||||
|
|
||||||
super(APIRouter, self).__init__(mapper)
|
super(APIRouter, self).__init__(mapper)
|
||||||
|
@ -674,6 +674,83 @@ class JSONV2TestCase(APIv2TestBase):
|
|||||||
self.assertEqual(res.status_int, 400)
|
self.assertEqual(res.status_int, 400)
|
||||||
|
|
||||||
|
|
||||||
|
class SubresourceTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
plugin = 'quantum.tests.unit.test_api_v2.TestSubresourcePlugin'
|
||||||
|
QuantumManager._instance = None
|
||||||
|
PluginAwareExtensionManager._instance = None
|
||||||
|
args = ['--config-file', etcdir('quantum.conf.test')]
|
||||||
|
config.parse(args=args)
|
||||||
|
cfg.CONF.set_override('core_plugin', plugin)
|
||||||
|
|
||||||
|
self._plugin_patcher = mock.patch(plugin, autospec=True)
|
||||||
|
self.plugin = self._plugin_patcher.start()
|
||||||
|
|
||||||
|
router.SUB_RESOURCES['dummy'] = {
|
||||||
|
'collection_name': 'dummies',
|
||||||
|
'parent': {'collection_name': 'networks',
|
||||||
|
'member_name': 'network'}
|
||||||
|
}
|
||||||
|
|
||||||
|
api = router.APIRouter()
|
||||||
|
self.api = webtest.TestApp(api)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self._plugin_patcher.stop()
|
||||||
|
self.api = None
|
||||||
|
self.plugin = None
|
||||||
|
cfg.CONF.reset()
|
||||||
|
|
||||||
|
def test_index_sub_resource(self):
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
|
||||||
|
self.api.get('/networks/id1/dummies')
|
||||||
|
instance.get_network_dummies.assert_called_once_with(mock.ANY,
|
||||||
|
filters=mock.ANY,
|
||||||
|
fields=mock.ANY,
|
||||||
|
network_id='id1')
|
||||||
|
|
||||||
|
def test_show_sub_resource(self):
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
|
||||||
|
dummy_id = _uuid()
|
||||||
|
self.api.get('/networks/id1' + _get_path('dummies', id=dummy_id))
|
||||||
|
instance.get_network_dummy.assert_called_once_with(mock.ANY,
|
||||||
|
dummy_id,
|
||||||
|
network_id='id1',
|
||||||
|
fields=mock.ANY)
|
||||||
|
|
||||||
|
def test_create_sub_resource(self):
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
|
||||||
|
body = {'dummy': {'foo': 'bar', 'tenant_id': _uuid()}}
|
||||||
|
self.api.post_json('/networks/id1/dummies', body)
|
||||||
|
instance.create_network_dummy.assert_called_once_with(mock.ANY,
|
||||||
|
network_id='id1',
|
||||||
|
dummy=body)
|
||||||
|
|
||||||
|
def test_update_sub_resource(self):
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
|
||||||
|
dummy_id = _uuid()
|
||||||
|
body = {'dummy': {'foo': 'bar', 'tenant_id': _uuid()}}
|
||||||
|
self.api.put_json('/networks/id1' + _get_path('dummies', id=dummy_id),
|
||||||
|
body)
|
||||||
|
instance.update_network_dummy.assert_called_once_with(mock.ANY,
|
||||||
|
dummy_id,
|
||||||
|
network_id='id1',
|
||||||
|
dummy=body)
|
||||||
|
|
||||||
|
def test_delete_sub_resource(self):
|
||||||
|
instance = self.plugin.return_value
|
||||||
|
|
||||||
|
dummy_id = _uuid()
|
||||||
|
self.api.delete('/networks/id1' + _get_path('dummies', id=dummy_id))
|
||||||
|
instance.delete_network_dummy.assert_called_once_with(mock.ANY,
|
||||||
|
dummy_id,
|
||||||
|
network_id='id1')
|
||||||
|
|
||||||
|
|
||||||
class V2Views(unittest.TestCase):
|
class V2Views(unittest.TestCase):
|
||||||
def _view(self, keys, collection, resource):
|
def _view(self, keys, collection, resource):
|
||||||
data = dict((key, 'value') for key in keys)
|
data = dict((key, 'value') for key in keys)
|
||||||
@ -861,3 +938,22 @@ class ExtensionTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(net['status'], "ACTIVE")
|
self.assertEqual(net['status'], "ACTIVE")
|
||||||
self.assertEqual(net['v2attrs:something'], "123")
|
self.assertEqual(net['v2attrs:something'], "123")
|
||||||
self.assertFalse('v2attrs:something_else' in net)
|
self.assertFalse('v2attrs:something_else' in net)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSubresourcePlugin():
|
||||||
|
def get_network_dummies(self, context, network_id,
|
||||||
|
filters=None, fields=None):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_network_dummy(self, context, id, network_id,
|
||||||
|
fields=None):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def create_network_dummy(self, context, network_id, dummy):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def update_network_dummy(self, context, id, network_id, dummy):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def delete_network_dummy(self, context, id, network_id):
|
||||||
|
return
|
||||||
|
Loading…
x
Reference in New Issue
Block a user