tricircle/tricircle/tests/unit/api/controllers/test_routing.py

441 lines
17 KiB
Python

# 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
from oslo_utils import uuidutils
import six
from six.moves import xrange
from oslo_config import cfg
import pecan
from tricircle.api import app
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 tricircle.tests import base
class FakeResponse(object):
def __new__(cls, code=500):
cls.status = code
cls.status_code = code
return super(FakeResponse, cls).__new__(cls)
class RoutingControllerTest(base.TestCase):
def setUp(self):
super(RoutingControllerTest, self).setUp()
cfg.CONF.clear()
cfg.CONF.register_opts(app.common_opts)
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(res[list(res.keys())[0]]['code'], 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
kw_routing = self._prepare_routing_element('subnet')
id = self.controller.post(**kw_routing)['routing']['id']
routing = db_api.get_resource_routing(self.context, id)
self.assertEqual('subnet', routing['resource_type'])
routings = db_api.list_resource_routings(self.context,
[{'key': 'resource_type',
'comparator': 'eq',
'value':
'subnet'
},
])
self.assertEqual(1, len(routings))
# failure case, only admin can create resource routing
self.context.is_admin = False
kw_routing = self._prepare_routing_element('subnet')
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': uuidutils.generate_uuid(),
'bottom_id': uuidutils.generate_uuid(),
}}
res = self.controller.post(**kw_routing1)
self._validate_error_code(res, 400)
# failure case, top_id is not given
kw_routing2 = self._prepare_routing_element('router')
kw_routing2['routing'].pop('top_id')
res = self.controller.post(**kw_routing2)
self._validate_error_code(res, 400)
# failure case, top_id is empty
kw_routing3 = self._prepare_routing_element('router')
kw_routing3['routing'].update({'top_id': ''})
res = self.controller.post(**kw_routing3)
self._validate_error_code(res, 400)
# failure case, top_id is given value 'None'
kw_routing4 = self._prepare_routing_element('security_group')
kw_routing4['routing'].update({'top_id': None})
res = self.controller.post(**kw_routing4)
self._validate_error_code(res, 400)
# failure case, wrong resource type
kw_routing6 = self._prepare_routing_element('server')
self.controller.post(**kw_routing6)
self._validate_error_code(res, 400)
# failure case, the resource routing already exists
kw_routing7 = self._prepare_routing_element('router')
self.controller.post(**kw_routing7)
res = self.controller.post(**kw_routing7)
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
kw_routing = self._prepare_routing_element('port')
id = self.controller.post(**kw_routing)['routing']['id']
routing = self.controller.get_one(id)
self.assertEqual('port', routing['routing']['resource_type'])
# 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_routings_with_pagination(self, mock_context):
self.context.project_id = uuidutils.generate_uuid()
mock_context.return_value = self.context
# test when no pagination and filters are applied to the list
# operation, then all of the routings will be retrieved.
count = 1
total_routings = 4
for resource_type in ('subnet', 'router', 'security_group', 'network'):
kw_routing = self._prepare_routing_element(resource_type)
# for test convenience, the first routing has a different
# project ID from later ones.
if count > 1:
kw_routing['routing']['project_id'] = self.context.project_id
self.controller.post(**kw_routing)
count += 1
routings = self.controller.get_all()
ids = [routing['id']
for key, values in six.iteritems(routings)
for routing in values]
self.assertEqual([4, 3, 2], ids)
for filter_name in ('router', 'security_group', 'network'):
filters = {'resource_type': filter_name}
routings = self.controller.get_all(**filters)
items = [routing['resource_type']
for key, values in six.iteritems(routings)
for routing in values]
self.assertEqual(1, len(items))
# test when pagination limit varies in range [1, total_routings+1)
for i in xrange(1, total_routings+1):
routings = []
total_pages = 0
routing = self.controller.get_all(limit=i)
total_pages += 1
routings.extend(routing['routings'])
while 'routings_links' in routing:
link = routing['routings_links'][0]['href']
_, marker_dict = link.split('&')
# link is like '/v1.0/routings?limit=1&marker=1', after split,
# marker_dict is a string like 'marker=1'.
_, marker_value = marker_dict.split('=')
routing = self.controller.get_all(limit=i, marker=marker_value)
if len(routing['routings']) > 0:
total_pages += 1
routings.extend(routing['routings'])
# assert that total pages will decrease as the limit increase.
# because the first routing has a different project ID and can't
# be retrieved by current admin role of project, so the number
# of actual total routings we can get is total_routings-1.
pages = int((total_routings - 1) / i)
if (total_routings - 1) % i:
pages += 1
self.assertEqual(pages, total_pages)
self.assertEqual(total_routings - 1, len(routings))
for i in xrange(total_routings-1):
self.assertEqual(total_routings - i, routings[i]['id'])
set1 = set(['router', 'security_group', 'network'])
set2 = set([routing1['resource_type'] for routing1 in routings])
self.assertEqual(set1, set2)
# test cases when pagination and filters are used
routings = self.controller.get_all(resource_type='network', limit=1)
self.assertEqual(1, len(routings['routings']))
routings = self.controller.get_all(resource_type='subnet', limit=2)
self.assertEqual(0, len(routings['routings']))
# 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)
# test cases when limit from client is abnormal
routings = self.controller.get_all(limit=0)
self.assertEqual(total_routings - 1, len(routings['routings']))
routings = self.controller.get_all(limit=-1)
self.assertEqual(total_routings - 1, len(routings['routings']))
res = self.controller.get_all(limit='20x')
self._validate_error_code(res, 400)
# test cases when pagination limit from client is greater than
# max limit
pagination_max_limit_backup = cfg.CONF.pagination_max_limit
cfg.CONF.set_override('pagination_max_limit', 2)
routings = self.controller.get_all(limit=3)
self.assertEqual(2, len(routings['routings']))
cfg.CONF.set_override('pagination_max_limit',
pagination_max_limit_backup)
# test case when marker reaches 1, then no link to next page
routings = self.controller.get_all(limit=2, marker=3)
self.assertNotIn('routings_links', routings)
# test cases when marker is abnormal
res = self.controller.get_all(limit=2, marker=-1)
self._validate_error_code(res, 400)
res = self.controller.get_all(limit=2, marker=0)
self._validate_error_code(res, 400)
res = self.controller.get_all(limit=2, marker="last")
self._validate_error_code(res, 400)
# failure case, use an unsupported filter type
kw_filter3 = {'resource': 'port'}
res = self.controller.get_all(**kw_filter3)
self._validate_error_code(res, 400)
kw_filter4 = {'pod_id': "pod_id_1",
'resource': 'port'}
res = self.controller.get_all(**kw_filter4)
self._validate_error_code(res, 400)
# failure case, id can't be converted to an integer
kw_filter5 = {'id': '4s'}
res = self.controller.get_all(**kw_filter5)
self._validate_error_code(res, 400)
# test when specify project ID filter from client, if this
# project ID is different from the one from context, then
# it will be ignored, project ID from context will be
# used instead.
res = self.controller.get_all()
kw_filter6 = {'project_id': uuidutils.generate_uuid()}
res1 = self.controller.get_all(**kw_filter6)
kw_filter7 = {'project_id': self.context.project_id}
res2 = self.controller.get_all(**kw_filter7)
self.assertEqual(len(res2['routings']), len(res1['routings']))
self.assertEqual(len(res['routings']), len(res2['routings']))
@patch.object(pecan, 'response', new=FakeResponse)
@patch.object(context, 'extract_context_from_environ')
def test_get_all_non_admin(self, mock_context):
mock_context.return_value = self.context
kw_routing1 = self._prepare_routing_element('subnet')
self.controller.post(**kw_routing1)
# 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
kw_pod = {'pod': {'region_name': 'pod1', 'az_name': 'az1'}}
pod_id = pod.PodsController().post(**kw_pod)['pod']['pod_id']
# a variable used for later test
project_id = uuidutils.generate_uuid()
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(200, res.status)
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(0, len(routings))
# 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
body = self._prepare_routing_element('subnet')
# both bottom_id and resource type have been changed
body_update1 = {'routing':
{'bottom_id': uuidutils.generate_uuid(),
'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(body_update1['routing']['bottom_id'],
routing['routing']['bottom_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': uuidutils.generate_uuid(),
'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 where the new pod_id lays on
# should exist in pod table
# a variable used for later test
new_pod_id = uuidutils.generate_uuid()
body_update6 = {'routing': {'pod_id': new_pod_id}}
res = self.controller.put(id, **body_update6)
self._validate_error_code(res, 400)
def _prepare_routing_element(self, resource_type):
"""Prepare routing fields except id
:return: A Dictionary with top_id, bottom_id, pod_id,
project_id, resource_type
"""
fake_routing = {
'routing': {
'top_id': uuidutils.generate_uuid(),
'bottom_id': uuidutils.generate_uuid(),
'pod_id': uuidutils.generate_uuid(),
'project_id': uuidutils.generate_uuid(),
'resource_type': resource_type,
}
}
return fake_routing
def tearDown(self):
cfg.CONF.unregister_opts(app.common_opts)
core.ModelBase.metadata.drop_all(core.get_engine())
super(RoutingControllerTest, self).tearDown()