# 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()