Implement resource routing features

1. What is the problem?
In order to dispatch resource operation request to the proper bottom instance,
we need a resource routing table, which maps a resource to the bottom instance
which it’s located at. However, the resource routing APIs have not been
implemented, so this patch carries out these features and performs some tests
for them at the same time.

2. What is the solution to the problem?
To implement the resource routing features, a new file named routing.py is
created in the directory tricircle/api/controllers, then regular post and
get functions will be implemented there. As for the unit test, a file named
test_routing.py is added to the directory tricircle/tests/unit/api/controllers,
conducting relevant tests for the features in file routing.py.

3. What the features need to be implemented to the Tricircle to realize
the solution?
Some main features like post, update, get and delete functions are implemented
in the file routing.py, then relative test method for each of them is realized
in the file test_routing.py. At last, these features are added to the search path
in the file root.py for REST API call.

Change-Id: I2c1073b8c8bd17eeb395afaa5ab7ea8d63609222
This commit is contained in:
Dongfeng Huang 2016-09-25 16:30:02 +08:00
parent d6e37673d5
commit e55e88fcae
7 changed files with 788 additions and 1 deletions

View File

@ -19,6 +19,7 @@ import pecan
from pecan import request
from tricircle.api.controllers import pod
from tricircle.api.controllers import routing
import tricircle.common.context as t_context
@ -77,7 +78,8 @@ class V1Controller(object):
self.sub_controllers = {
"pods": pod.PodsController(),
"bindings": pod.BindingsController()
"bindings": pod.BindingsController(),
"routings": routing.RoutingController()
}
for name, ctrl in self.sub_controllers.items():

View File

@ -0,0 +1,247 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pecan
from pecan import expose
from pecan import rest
from oslo_log import log as logging
from tricircle.common import constants
import tricircle.common.context as t_context
import tricircle.common.exceptions as t_exc
from tricircle.common.i18n import _
from tricircle.common.i18n import _LE
from tricircle.common import policy
from tricircle.common import utils
from tricircle.db import api as db_api
LOG = logging.getLogger(__name__)
SUPPORTED_FILTERS = ['id', 'top_id', 'bottom_id', 'pod_id', 'project_id',
'resource_type', 'created_at', 'updated_at']
class RoutingController(rest.RestController):
def __init__(self):
pass
@expose(generic=True, template='json')
def post(self, **kw):
context = t_context.extract_context_from_environ()
if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_CREATE):
return utils.format_api_error(
403, _("Unauthorized to create resource routing"))
if 'routing' not in kw:
return utils.format_api_error(
400, _("Request body not found"))
routing = kw['routing']
for field in ('top_id', 'bottom_id', 'pod_id',
'project_id', 'resource_type'):
value = routing.get(field)
if value is None or len(value.strip()) == 0:
return utils.format_api_error(
400, _("Field %(field)s can not be empty") % {
'field': field})
# verify the integrity: the pod_id and the project_id should be bound
pod_id = routing.get('pod_id').strip()
project_id = routing.get('project_id').strip()
bindings = db_api.list_pod_bindings(context,
[{'key': 'pod_id',
'comparator': 'eq',
'value': pod_id
},
{'key': 'tenant_id',
'comparator': 'eq',
'value': project_id}
], [])
if len(bindings) == 0:
return utils.format_api_error(
400, _('The pod_id and project_id have not been bound'))
# the resource type should be properly provisioned.
resource_type = routing.get('resource_type').strip()
if not constants.is_valid_resource_type(resource_type):
return utils.format_api_error(
400, _('There is no such resource type'))
try:
top_id = routing.get('top_id').strip()
bottom_id = routing.get('bottom_id').strip()
routing = db_api.create_resource_mapping(context, top_id,
bottom_id, pod_id,
project_id,
resource_type)
if not routing:
return utils.format_api_error(
409, _('Resource routing already exists'))
except Exception as e:
LOG.exception(_LE('Failed to create resource routing: '
'%(exception)s '), {'exception': e})
return utils.format_api_error(
500, _('Failed to create resource routing'))
return {'routing': routing}
def _get_filters(self, params):
"""Return a dictionary of query param filters from the request.
:param params: the URI params coming from the wsgi layer
:return a dict of key/value filters
"""
filters = {}
for param in params:
if param in SUPPORTED_FILTERS:
# map filter name
filter_name = param
filters[filter_name] = params.get(param)
return filters
@expose(generic=True, template='json')
def get_all(self, **kwargs):
context = t_context.extract_context_from_environ()
if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_LIST):
return utils.format_api_error(
403, _('Unauthorized to to show all resource routings'))
filters = self._get_filters(kwargs)
filters = [{'key': key,
'comparator': 'eq',
'value': value} for key, value in filters.iteritems()]
try:
return {'routings': db_api.list_resource_routings(context,
filters)}
except Exception as e:
LOG.exception(_LE('Failed to show all resource routings: '
'%(exception)s '), {'exception': e})
return utils.format_api_error(
500, _('Failed to show all resource routings'))
@expose(generic=True, template='json')
def get_one(self, _id):
context = t_context.extract_context_from_environ()
if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_SHOW):
return utils.format_api_error(
403, _('Unauthorized to show the resource routing'))
try:
return {'routing': db_api.get_resource_routing(context, _id)}
except t_exc.ResourceNotFound:
return utils.format_api_error(
404, _('Resource routing not found'))
@expose(generic=True, template='json')
def delete(self, _id):
context = t_context.extract_context_from_environ()
if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_DELETE):
return utils.format_api_error(
403, _('Unauthorized to delete the resource routing'))
try:
db_api.get_resource_routing(context, _id)
except t_exc.ResourceNotFound:
return utils.format_api_error(404,
_('Resource routing not found'))
try:
db_api.delete_resource_routing(context, _id)
pecan.response.status = 200
return pecan.response
except Exception as e:
LOG.exception(_LE('Failed to delete the resource routing: '
'%(exception)s '), {'exception': e})
return utils.format_api_error(
500, _('Failed to delete the resource routing'))
@expose(generic=True, template='json')
def put(self, _id, **kw):
context = t_context.extract_context_from_environ()
if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_PUT):
return utils.format_api_error(
403, _('Unauthorized to update resource routing'))
try:
routing = db_api.get_resource_routing(context, _id)
except t_exc.ResourceNotFound:
return utils.format_api_error(404,
_('Resource routing not found'))
if 'routing' not in kw:
return utils.format_api_error(
400, _('Request body not found'))
update_dict = kw['routing']
# values to be updated should not be empty
for field in update_dict:
value = update_dict.get(field)
if value is None or len(value.strip()) == 0:
return utils.format_api_error(
400, _("Field %(field)s can not be empty") % {
'field': field})
# the resource type should be properly provisioned.
if 'resource_type' in update_dict:
if not constants.is_valid_resource_type(
update_dict['resource_type']):
return utils.format_api_error(
400, _('There is no such resource type'))
# verify the integrity: the pod_id and project_id should be bound
if 'pod_id' in update_dict or 'project_id' in update_dict:
if 'pod_id' in update_dict:
pod_id = update_dict['pod_id']
else:
pod_id = routing['pod_id']
if 'project_id' in update_dict:
project_id = update_dict['project_id']
else:
project_id = routing['project_id']
bindings = db_api.list_pod_bindings(context,
[{'key': 'pod_id',
'comparator': 'eq',
'value': pod_id
},
{'key': 'tenant_id',
'comparator': 'eq',
'value': project_id}
], [])
if len(bindings) == 0:
return utils.format_api_error(
400, _('The pod_id and project_id have not been '
'bound'))
try:
routing_updated = db_api.update_resource_routing(
context, _id, update_dict)
return {'routing': routing_updated}
except Exception as e:
LOG.exception(_LE('Failed to update resource routing: '
'%(exception)s '), {'exception': e})
return utils.format_api_error(
500, _('Failed to update resource routing'))

View File

@ -35,6 +35,13 @@ RT_PORT = 'port'
RT_ROUTER = 'router'
RT_SG = 'security_group'
# check whether the resource type is properly provisioned.
def is_valid_resource_type(resource_type):
resource_type_table = [RT_NETWORK, RT_SUBNET, RT_PORT, RT_ROUTER, RT_SG]
return resource_type in resource_type_table
# version list
NOVA_VERSION_V21 = 'v2.1'
CINDER_VERSION_V2 = 'v2'

View File

@ -57,6 +57,12 @@ ADMIN_API_BINDINGS_DELETE = 'admin_api:bindings:delete'
ADMIN_API_BINDINGS_SHOW = 'admin_api:bindings:show'
ADMIN_API_BINDINGS_LIST = 'admin_api:bindings:list'
ADMIN_API_ROUTINGS_CREATE = 'admin_api:routings:create'
ADMIN_API_ROUTINGS_DELETE = 'admin_api:routings:delete'
ADMIN_API_ROUTINGS_PUT = 'admin_api:routings:put'
ADMIN_API_ROUTINGS_SHOW = 'admin_api:routings:show'
ADMIN_API_ROUTINGS_LIST = 'admin_api:routings:list'
tricircle_admin_api_policies = [
policy.RuleDefault(ADMIN_API_PODS_CREATE,
'rule:admin_api',
@ -83,6 +89,22 @@ tricircle_admin_api_policies = [
policy.RuleDefault(ADMIN_API_BINDINGS_LIST,
'rule:admin_api',
description='List pod bindings'),
policy.RuleDefault(ADMIN_API_ROUTINGS_CREATE,
'rule:admin_api',
description='Create resource routing'),
policy.RuleDefault(ADMIN_API_ROUTINGS_DELETE,
'rule:admin_api',
description='Delete resource routing'),
policy.RuleDefault(ADMIN_API_ROUTINGS_PUT,
'rule:admin_api',
description='Update resource routing'),
policy.RuleDefault(ADMIN_API_ROUTINGS_SHOW,
'rule:admin_api',
description='Show resource routing detail'),
policy.RuleDefault(ADMIN_API_ROUTINGS_LIST,
'rule:admin_api',
description='List resource routings'),
]

View File

@ -159,6 +159,10 @@ def format_error(code, message, error_type=None):
return {error_type: {'message': message, 'code': code}}
def format_api_error(code, message, error_type=None):
return format_error(code, message, error_type)
def format_nova_error(code, message, error_type=None):
return format_error(code, message, error_type)

View File

@ -102,6 +102,12 @@ def create_pod_binding(context, tenant_id, pod_id):
'is_binding': True})
def list_pod_bindings(context, filters=None, sorts=None):
with context.session.begin():
return core.query_resource(context, models.PodBinding,
filters or [], sorts or [])
def delete_pod_service_configuration(context, config_id):
with context.session.begin():
return core.delete_resource(context, models.PodServiceConfiguration,
@ -145,6 +151,28 @@ def create_resource_mapping(context, top_id, bottom_id, pod_id, project_id,
context.session.close()
def list_resource_routings(context, filters=None, sorts=None):
with context.session.begin():
return core.query_resource(context, models.ResourceRouting,
filters or [], sorts or [])
def get_resource_routing(context, id):
with context.session.begin():
return core.get_resource(context, models.ResourceRouting, id)
def delete_resource_routing(context, id):
with context.session.begin():
return core.delete_resource(context, models.ResourceRouting, id)
def update_resource_routing(context, id, update_dict):
with context.session.begin():
return core.update_resource(context, models.ResourceRouting, id,
update_dict)
def get_bottom_mappings_by_top_id(context, top_id, resource_type):
"""Get resource id and pod name on bottom

View File

@ -0,0 +1,477 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from mock import patch
import unittest
import pecan
from tricircle.api.controllers import pod
from tricircle.api.controllers import routing
from tricircle.common import context
from tricircle.common import policy
from tricircle.db import api as db_api
from tricircle.db import core
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class FakeResponse(object):
def __new__(cls, code=500):
cls.status = code
cls.status_code = code
return super(FakeResponse, cls).__new__(cls)
class RoutingControllerTest(unittest.TestCase):
def setUp(self):
core.initialize()
core.ModelBase.metadata.create_all(core.get_engine())
self.controller = routing.RoutingController()
self.context = context.get_admin_context()
policy.populate_default_rules()
def _validate_error_code(self, res, code):
self.assertEqual(code, res[res.keys()[0]]['code'])
@patch.object(pecan, 'response', new=FakeResponse)
@patch.object(context, 'extract_context_from_environ')
def test_post(self, mock_context):
mock_context.return_value = self.context
# prepare the foreign key: pod_id, project_id
kw_pod = {'pod': {'pod_name': 'pod1', 'az_name': 'az1'}}
pod_id = pod.PodsController().post(**kw_pod)['pod']['pod_id']
kw_binding = {'pod_binding': {'tenant_id': '01', 'pod_id': pod_id}}
project_id = pod.BindingsController().post(**kw_binding)[
'pod_binding']['tenant_id']
kw_routing = {'routing':
{'top_id': '09fd7cc9-d169-4b5a-88e8-436ecf4d0bfe',
'bottom_id': 'dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id,
'project_id': project_id,
'resource_type': 'subnet'
}}
id = self.controller.post(**kw_routing)['routing']['id']
routing = db_api.get_resource_routing(self.context, id)
self.assertEqual(routing['top_id'],
'09fd7cc9-d169-4b5a-88e8-436ecf4d0bfe')
self.assertEqual(routing['bottom_id'],
'dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef')
self.assertEqual(routing['pod_id'], pod_id)
self.assertEqual(routing['project_id'], project_id)
self.assertEqual(routing['resource_type'], 'subnet')
routings = db_api.list_resource_routings(self.context,
[{'key': 'top_id',
'comparator': 'eq',
'value':
'09fd7cc9-d169-4b5a-'
'88e8-436ecf4d0bfe'
},
{'key': 'pod_id',
'comparator': 'eq',
'value': pod_id}
], [])
self.assertEqual(len(routings), 1)
# failure case, only admin can create resource routing
self.context.is_admin = False
res = self.controller.post(**kw_routing)
self._validate_error_code(res, 403)
self.context.is_admin = True
# failure case, request body not found
kw_routing1 = {'route':
{'top_id': '109fd7cc9-d169-4b5a-88e8-436ecf4d0bfe',
'bottom_id': '2dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id,
'project_id': project_id,
'resource_type': 'subnet'
}}
res = self.controller.post(**kw_routing1)
self._validate_error_code(res, 400)
# failure case, top_id is not given
kw_routing2 = {'routing':
{'bottom_id': '2dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id,
'project_id': project_id,
'resource_type': 'subnet'
}}
res = self.controller.post(**kw_routing2)
self._validate_error_code(res, 400)
# failure case, top_id is empty
kw_routing3 = {'routing':
{'top_id': '',
'bottom_id': '2dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id,
'project_id': project_id,
'resource_type': 'subnet'
}}
res = self.controller.post(**kw_routing3)
self._validate_error_code(res, 400)
# failure case, top_id is given value 'None'
kw_routing4 = {'routing':
{'top_id': None,
'bottom_id': '2dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id,
'project_id': project_id,
'resource_type': 'subnet'
}}
res = self.controller.post(**kw_routing4)
self._validate_error_code(res, 400)
# failure case, the pod_id and the project_id should be bound
kw_pod2 = {'pod': {'pod_name': 'pod2', 'az_name': 'az1'}}
pod_id2 = pod.PodsController().post(**kw_pod2)['pod']['pod_id']
# the tenant_id binds with pod_id rather than pod_id2
kw_binding2 = {'pod_binding': {'tenant_id': '02', 'pod_id': pod_id}}
project_id2 = pod.BindingsController().post(**kw_binding2)[
'pod_binding']['tenant_id']
kw_routing5 = {'routing':
{'top_id': '09fd7cc9-d169-4b5a-88e8-436ecf4d0bfe',
'bottom_id': 'dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id2,
'project_id': project_id2,
'resource_type': 'subnet'
}}
res = self.controller.post(**kw_routing5)
self._validate_error_code(res, 400)
# failure case, wrong resource type
kw_routing6 = {'routing':
{'top_id': '09fd7cc9-d169-4b5a-88e8-436ecf4d0b09',
'bottom_id': 'dc80f9de-abb7-4ec6-ab7a-94f8fd1e2031f',
'pod_id': pod_id,
'project_id': project_id,
'resource_type': 'server'
}}
res = self.controller.post(**kw_routing6)
self._validate_error_code(res, 400)
# failure case, the resource routing already exists
res = self.controller.post(**kw_routing)
self._validate_error_code(res, 409)
@patch.object(pecan, 'response', new=FakeResponse)
@patch.object(context, 'extract_context_from_environ')
def test_get_one(self, mock_context):
mock_context.return_value = self.context
# prepare the foreign key: pod_id, project_id
kw_pod = {'pod': {'pod_name': 'pod1', 'az_name': 'az1'}}
pod_id = pod.PodsController().post(**kw_pod)['pod']['pod_id']
kw_binding = {'pod_binding': {'tenant_id': '01', 'pod_id': pod_id}}
project_id = pod.BindingsController().post(**kw_binding)[
'pod_binding']['tenant_id']
kw_routing = {'routing':
{'top_id': '09fd7cc9-d169-4b5a-88e8-436ecf4d0bfe',
'bottom_id': 'dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id,
'project_id': project_id,
'resource_type': 'port'
}}
id = self.controller.post(**kw_routing)['routing']['id']
routing = self.controller.get_one(id)
self.assertEqual(routing['routing']['top_id'],
'09fd7cc9-d169-4b5a-88e8-436ecf4d0bfe')
self.assertEqual(routing['routing']['bottom_id'],
'dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef')
self.assertEqual(routing['routing']['pod_id'], pod_id)
self.assertEqual(routing['routing']['project_id'],
project_id)
self.assertEqual(routing['routing']['resource_type'],
'port')
# failure case, only admin can get resource routing
self.context.is_admin = False
res = self.controller.get_one(id)
self._validate_error_code(res, 403)
self.context.is_admin = True
# failure case, resource routing not found
res = self.controller.get_one(-123)
self._validate_error_code(res, 404)
@patch.object(pecan, 'response', new=FakeResponse)
@patch.object(context, 'extract_context_from_environ')
def test_get_all(self, mock_context):
mock_context.return_value = self.context
# prepare the foreign key: pod_id, project_id
kw_pod1 = {'pod': {'pod_name': 'pod1', 'az_name': 'az1'}}
pod_id1 = pod.PodsController().post(**kw_pod1)['pod']['pod_id']
kw_binding1 = {'pod_binding': {'tenant_id': '01', 'pod_id': pod_id1}}
project_id1 = pod.BindingsController().post(**kw_binding1)[
'pod_binding']['tenant_id']
kw_routing1 = {'routing':
{'top_id': 'c7f641c9-8462-4007-84b2-3035d8cfb7a3',
'bottom_id': 'dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id1,
'project_id': project_id1,
'resource_type': 'subnet'
}}
# prepare the foreign key: pod_id, project_id
kw_pod2 = {'pod': {'pod_name': 'pod2', 'az_name': 'az1'}}
pod_id2 = pod.PodsController().post(**kw_pod2)['pod']['pod_id']
kw_binding2 = {'pod_binding': {'tenant_id': '02', 'pod_id': pod_id2}}
project_id2 = pod.BindingsController().post(**kw_binding2)[
'pod_binding']['tenant_id']
kw_routing2 = {'routing':
{'top_id': 'b669a2da-ca95-47db-a2a9-ba9e546d82ee',
'bottom_id': 'fd72c010-6e62-4866-b999-6dcb718dd7b4',
'pod_id': pod_id2,
'project_id': project_id2,
'resource_type': 'port'
}}
self.controller.post(**kw_routing1)
self.controller.post(**kw_routing2)
# no filters are applied to the routings, so all of the routings will
# be retrieved.
routings = self.controller.get_all()
actual = [(routing['top_id'], routing['pod_id'])
for routing in routings['routings']]
expect = [('c7f641c9-8462-4007-84b2-3035d8cfb7a3', pod_id1),
('b669a2da-ca95-47db-a2a9-ba9e546d82ee', pod_id2)]
self.assertItemsEqual(expect, actual)
# apply a resource type filter to the retrieved routings.
kw_filter1 = {'resource_type': 'port'}
routings = self.controller.get_all(**kw_filter1)
actual = [(routing['top_id'], routing['pod_id'],
routing['resource_type'])
for routing in routings['routings']]
expect = [('b669a2da-ca95-47db-a2a9-ba9e546d82ee', pod_id2, 'port')]
self.assertItemsEqual(expect, actual)
# apply a filter and if it doesn't match with any of the retrieved
# routings, then all of them will be discarded and the method returns
# with []
kw_filter2 = {'resource_type': 'port2'}
routings = self.controller.get_all(**kw_filter2)
self.assertEqual(routings['routings'], [])
# apply an illegal filter and it won't take effect
kw_filter3 = {'resource': 'port'}
routings = self.controller.get_all(**kw_filter3)
actual = [(routing['top_id'], routing['pod_id'])
for routing in routings['routings']]
expect = [('c7f641c9-8462-4007-84b2-3035d8cfb7a3', pod_id1),
('b669a2da-ca95-47db-a2a9-ba9e546d82ee', pod_id2)]
self.assertItemsEqual(expect, actual)
# failure case, only admin can show all resource routings
self.context.is_admin = False
res = self.controller.get_all()
self._validate_error_code(res, 403)
self.context.is_admin = True
@patch.object(pecan, 'response', new=FakeResponse)
@patch.object(pecan, 'response', new=mock.Mock)
@patch.object(context, 'extract_context_from_environ')
def test_delete(self, mock_context):
mock_context.return_value = self.context
# prepare the foreign key: pod_id, project_id
kw_pod = {'pod': {'pod_name': 'pod1', 'az_name': 'az1'}}
pod_id = pod.PodsController().post(**kw_pod)['pod']['pod_id']
kw_binding = {'pod_binding': {'tenant_id': '01', 'pod_id': pod_id}}
project_id = pod.BindingsController().post(**kw_binding)[
'pod_binding']['tenant_id']
kw_routing = {'routing':
{'top_id': '09fd7cc9-d169-4b5a-88e8-436ecf4d0bfe',
'bottom_id': 'dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id,
'project_id': project_id,
'resource_type': 'subnet'
}}
routing = self.controller.post(**kw_routing)
id = routing['routing']['id']
res = self.controller.delete(id)
self.assertEqual(res.status, 200)
routings = db_api.list_resource_routings(self.context,
[{'key': 'top_id',
'comparator': 'eq',
'value': '09fd7cc9-d'
'169-4b5a-88e8-436ecf4d0bfe'
},
{'key': 'pod_id',
'comparator': 'eq',
'value': pod_id
}], [])
self.assertEqual(len(routings), 0)
# failure case, only admin can delete resource routing
self.context.is_admin = False
res = self.controller.delete(id)
self._validate_error_code(res, 403)
self.context.is_admin = True
# failure case, resource routing not found
res = self.controller.delete(-123)
self._validate_error_code(res, 404)
@patch.object(pecan, 'response', new=FakeResponse)
@patch.object(pecan, 'response', new=mock.Mock)
@patch.object(context, 'extract_context_from_environ')
def test_put(self, mock_context):
mock_context.return_value = self.context
# prepare the foreign key: pod_id, project_id
kw_pod1 = {'pod': {'pod_name': 'pod1', 'az_name': 'az1'}}
pod_id1 = pod.PodsController().post(**kw_pod1)['pod']['pod_id']
kw_binding1 = {'pod_binding': {'tenant_id': '01', 'pod_id': pod_id1}}
project_id1 = pod.BindingsController().post(**kw_binding1)[
'pod_binding']['tenant_id']
body = {'routing':
{'top_id': 'c7f641c9-8462-4007-84b2-3035d8cfb7a3',
'bottom_id': 'dc80f9de-abb7-4ec6-ab7a-94f8fd1e20ef',
'pod_id': pod_id1,
'project_id': project_id1,
'resource_type': 'router'
}}
# both bottom_id and resource type have been changed
body_update1 = {'routing':
{'bottom_id': 'fd72c010-6e62-4866-b999-6dcb718dd7b4',
'resource_type': 'port'
}}
id = self.controller.post(**body)['routing']['id']
routing = self.controller.put(id, **body_update1)
self.assertEqual('port',
routing['routing']['resource_type'])
self.assertEqual('fd72c010-6e62-4866-b999-6dcb718dd7b4',
routing['routing']['bottom_id'])
self.assertEqual(pod_id1, routing['routing']['pod_id'])
# failure case, only admin can update resource routing
self.context.is_admin = False
res = self.controller.put(id, **body_update1)
self._validate_error_code(res, 403)
self.context.is_admin = True
# failure case, request body not found
body_update2 = {'route':
{'bottom_id': 'fd72c010-6e62-4866-b999-6dcb718dd7b4',
'resource_type': 'port'
}}
res = self.controller.put(id, **body_update2)
self._validate_error_code(res, 400)
# failure case, wrong resource type
body_update3 = {'routing':
{'resource_type': 'volume'}}
res = self.controller.put(id, **body_update3)
self._validate_error_code(res, 400)
# failure case, the value to be updated is empty
body_update4 = {'routing':
{'top_id': ''}}
res = self.controller.put(id, **body_update4)
self._validate_error_code(res, 400)
# failure case, the value to be updated is None
body_update5 = {'routing':
{'top_id': None}}
res = self.controller.put(id, **body_update5)
self._validate_error_code(res, 400)
# failure case, the value to be updated is not appropriate
res = self.controller.put(-123, **body_update1)
self._validate_error_code(res, 404)
# failure case, the pod_id and the project_id should be bound
kw_pod2 = {'pod': {'pod_name': 'pod2', 'az_name': 'az1'}}
pod_id2 = pod.PodsController().post(**kw_pod2)['pod']['pod_id']
# only update pod_id, and the new pod id is pod_id2. pod_id2
# has not been bound to tenant whose tenant_id is project_id1
body_update6 = {'routing':
{'pod_id': pod_id2}}
res = self.controller.put(id, **body_update6)
self._validate_error_code(res, 400)
# failure case, the pod_id and the project_id should be bound
kw_binding2 = {'pod_binding': {'tenant_id': '02', 'pod_id': pod_id2}}
project_id2 = pod.BindingsController().post(**kw_binding2)[
'pod_binding']['tenant_id']
# only update project_id, and the new project id is project_id2,
# this tenant has not been bound to pod whose pod_id is pod_id1
body_update7 = {'routing':
{'project_id': project_id2}}
res = self.controller.put(id, **body_update7)
self._validate_error_code(res, 400)
# failure case, the pod_id and the project_id should be bound
# both the pod_id and project_id are specified, and the
# tenant1 bound to pod1, pod3, tenant2 bound to pod1, pod2
# original routing: pod=pod1, tenant=tenant1
# updated routing: pod=pod3, tenant=tenant2
# this case should be failed
# bind the tenant1 to pod3
kw_pod3 = {'pod': {'pod_name': 'pod3', 'az_name': 'az1'}}
pod_id3 = pod.PodsController().post(**kw_pod3)['pod']['pod_id']
kw_binding3 = {'pod_binding': {'tenant_id': '01', 'pod_id': pod_id3}}
pod_id3 = pod.BindingsController().post(**kw_binding3)[
'pod_binding']['pod_id']
# bind the tenant2 to pod1
kw_binding4 = {'pod_binding': {'tenant_id': '02', 'pod_id': pod_id1}}
project_id2 = pod.BindingsController().post(**kw_binding4)[
'pod_binding']['tenant_id']
body_update8 = {'routing':
{'pod_id': pod_id3,
'project_id': project_id2}}
res = self.controller.put(id, **body_update8)
self._validate_error_code(res, 400)
def tearDown(self):
core.ModelBase.metadata.drop_all(core.get_engine())