Merge "Add quota tests in api tests."
diff --git a/neutron/tests/tempest/api/base.py b/neutron/tests/tempest/api/base.py
index c1bd622..033ac67 100644
--- a/neutron/tests/tempest/api/base.py
+++ b/neutron/tests/tempest/api/base.py
@@ -13,11 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
+import functools
+
import netaddr
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
from tempest import test
+from neutron.common import constants
from neutron.tests.tempest.api import clients
from neutron.tests.tempest import config
from neutron.tests.tempest import exceptions
@@ -460,3 +463,129 @@
message = (
"net(%s) has no usable IP address in allocation pools" % net_id)
raise exceptions.InvalidConfiguration(message)
+
+
+def _require_sorting(f):
+ @functools.wraps(f)
+ def inner(self, *args, **kwargs):
+ if not CONF.neutron_plugin_options.validate_sorting:
+ self.skipTest('Sorting feature is required')
+ return f(self, *args, **kwargs)
+ return inner
+
+
+def _require_pagination(f):
+ @functools.wraps(f)
+ def inner(self, *args, **kwargs):
+ if not CONF.neutron_plugin_options.validate_pagination:
+ self.skipTest('Pagination feature is required')
+ return f(self, *args, **kwargs)
+ return inner
+
+
+class BaseSearchCriteriaTest(BaseNetworkTest):
+
+ # This should be defined by subclasses to reflect resource name to test
+ resource = None
+
+ # also test a case when there are multiple resources with the same name
+ resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
+
+ list_kwargs = {'shared': False}
+
+ force_tenant_isolation = True
+
+ @classmethod
+ def resource_setup(cls):
+ super(BaseSearchCriteriaTest, cls).resource_setup()
+
+ cls.create_method = getattr(cls, 'create_%s' % cls.resource)
+
+ # NOTE(ihrachys): some names, like those starting with an underscore
+ # (_) are sorted differently depending on whether the plugin implements
+ # native sorting support, or not. So we avoid any such cases here,
+ # sticking to alphanumeric.
+ for name in cls.resource_names:
+ args = {'%s_name' % cls.resource: name}
+ cls.create_method(**args)
+
+ def list_method(self, *args, **kwargs):
+ method = getattr(self.client, 'list_%ss' % self.resource)
+ kwargs.update(self.list_kwargs)
+ return method(*args, **kwargs)
+
+ @classmethod
+ def _extract_resources(cls, body):
+ return body['%ss' % cls.resource]
+
+ def _test_list_sorts(self, direction):
+ sort_args = {
+ 'sort_dir': direction,
+ 'sort_key': 'name'
+ }
+ body = self.list_method(**sort_args)
+ resources = self._extract_resources(body)
+ self.assertNotEmpty(
+ resources, "%s list returned is empty" % self.resource)
+ retrieved_names = [res['name'] for res in resources]
+ expected = sorted(retrieved_names)
+ if direction == constants.SORT_DIRECTION_DESC:
+ expected = list(reversed(expected))
+ self.assertEqual(expected, retrieved_names)
+
+ @_require_sorting
+ def _test_list_sorts_asc(self):
+ self._test_list_sorts(constants.SORT_DIRECTION_ASC)
+
+ @_require_sorting
+ def _test_list_sorts_desc(self):
+ self._test_list_sorts(constants.SORT_DIRECTION_DESC)
+
+ @_require_pagination
+ def _test_list_pagination(self):
+ for limit in range(1, len(self.resource_names) + 1):
+ pagination_args = {
+ 'limit': limit,
+ }
+ body = self.list_method(**pagination_args)
+ resources = self._extract_resources(body)
+ self.assertEqual(limit, len(resources))
+
+ @_require_pagination
+ def _test_list_no_pagination_limit_0(self):
+ pagination_args = {
+ 'limit': 0,
+ }
+ body = self.list_method(**pagination_args)
+ resources = self._extract_resources(body)
+ self.assertTrue(len(resources) >= len(self.resource_names))
+
+ @_require_pagination
+ @_require_sorting
+ def _test_list_pagination_with_marker(self):
+ # first, collect all resources for later comparison
+ sort_args = {
+ 'sort_dir': constants.SORT_DIRECTION_ASC,
+ 'sort_key': 'name'
+ }
+ body = self.list_method(**sort_args)
+ expected_resources = self._extract_resources(body)
+ self.assertNotEmpty(expected_resources)
+
+ # paginate resources one by one, using last fetched resource as a
+ # marker
+ resources = []
+ for i in range(len(expected_resources)):
+ pagination_args = sort_args.copy()
+ pagination_args['limit'] = 1
+ if resources:
+ pagination_args['marker'] = resources[-1]['id']
+ body = self.list_method(**pagination_args)
+ resources_ = self._extract_resources(body)
+ self.assertEqual(1, len(resources_))
+ resources.extend(resources_)
+
+ # finally, compare that the list retrieved in one go is identical to
+ # the one containing pagination results
+ for expected, res in zip(expected_resources, resources):
+ self.assertEqual(expected['name'], res['name'])
diff --git a/neutron/tests/tempest/api/test_networks.py b/neutron/tests/tempest/api/test_networks.py
index b96a5bd..4816d79 100644
--- a/neutron/tests/tempest/api/test_networks.py
+++ b/neutron/tests/tempest/api/test_networks.py
@@ -89,3 +89,33 @@
self.assertNotEmpty(networks, "Network list returned is empty")
for network in networks:
self.assertEqual(sorted(network.keys()), sorted(fields))
+
+
+class NetworksSearchCriteriaTest(base.BaseSearchCriteriaTest):
+
+ resource = 'network'
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('de27d34a-bd9d-4516-83d6-81ef723f7d0d')
+ def test_list_sorts_asc(self):
+ self._test_list_sorts_asc()
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('e767a160-59f9-4c4b-8dc1-72124a68640a')
+ def test_list_sorts_desc(self):
+ self._test_list_sorts_desc()
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('71389852-f57b-49f2-b109-77b705e9e8af')
+ def test_list_pagination(self):
+ self._test_list_pagination()
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('71389852-f57b-49f2-b109-77b705e9e8af')
+ def test_list_pagination_with_marker(self):
+ self._test_list_pagination_with_marker()
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('f1867fc5-e1d6-431f-bc9f-8b882e43a7f9')
+ def test_list_no_pagination_limit_0(self):
+ self._test_list_no_pagination_limit_0()
diff --git a/neutron/tests/tempest/api/test_networks_negative.py b/neutron/tests/tempest/api/test_networks_negative.py
new file mode 100644
index 0000000..87da1b8
--- /dev/null
+++ b/neutron/tests/tempest/api/test_networks_negative.py
@@ -0,0 +1,36 @@
+# 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.
+
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+import testtools
+
+from neutron.tests.tempest.api import base
+
+
+class NetworksNegativeTest(base.BaseNetworkTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(NetworksNegativeTest, cls).resource_setup()
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+
+ @test.attr(type='negative')
+ @test.idempotent_id('9f80f25b-5d1b-4f26-9f6b-774b9b270819')
+ def test_delete_network_in_use(self):
+ port = self.client.create_port(network_id=self.network['id'])
+ self.addCleanup(self.client.delete_port, port['port']['id'])
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.client.delete_subnet(self.subnet['id'])
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.client.delete_network(self.network['id'])
diff --git a/neutron/tests/tempest/api/test_ports.py b/neutron/tests/tempest/api/test_ports.py
index dd929da..7bf4790 100644
--- a/neutron/tests/tempest/api/test_ports.py
+++ b/neutron/tests/tempest/api/test_ports.py
@@ -39,3 +39,10 @@
self.assertEqual('d2', body['port']['description'])
body = self.client.list_ports(id=body['port']['id'])['ports'][0]
self.assertEqual('d2', body['description'])
+
+ @test.idempotent_id('c72c1c0c-2193-4aca-bbb4-b1442640c123')
+ def test_change_dhcp_flag_then_create_port(self):
+ s = self.create_subnet(self.network, enable_dhcp=False)
+ self.create_port(self.network)
+ self.client.update_subnet(s['id'], enable_dhcp=True)
+ self.create_port(self.network)
diff --git a/neutron/tests/tempest/api/test_routers_negative.py b/neutron/tests/tempest/api/test_routers_negative.py
index 2ccb34a..6a028db 100644
--- a/neutron/tests/tempest/api/test_routers_negative.py
+++ b/neutron/tests/tempest/api/test_routers_negative.py
@@ -21,20 +21,41 @@
from neutron.tests.tempest.api import base_routers as base
-class DvrRoutersNegativeTest(base.BaseRouterTest):
+class RoutersNegativeTestBase(base.BaseRouterTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(RoutersNegativeTestBase, cls).resource_setup()
+ cls.router = cls.create_router(data_utils.rand_name('router'))
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+
+
+class RoutersNegativeTest(RoutersNegativeTestBase):
+
+ @classmethod
+ @test.requires_ext(extension="router", service="network")
+ def skip_checks(cls):
+ super(RoutersNegativeTest, cls).skip_checks()
+
+ @test.attr(type='negative')
+ @test.idempotent_id('e3e751af-15a2-49cc-b214-a7154579e94f')
+ def test_delete_router_in_use(self):
+ # This port is deleted after a test by remove_router_interface.
+ port = self.client.create_port(network_id=self.network['id'])
+ self.client.add_router_interface_with_port_id(
+ self.router['id'], port['port']['id'])
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.client.delete_router(self.router['id'])
+
+
+class DvrRoutersNegativeTest(RoutersNegativeTestBase):
@classmethod
@test.requires_ext(extension="dvr", service="network")
def skip_checks(cls):
super(DvrRoutersNegativeTest, cls).skip_checks()
- @classmethod
- def resource_setup(cls):
- super(DvrRoutersNegativeTest, cls).resource_setup()
- cls.router = cls.create_router(data_utils.rand_name('router'))
- cls.network = cls.create_network()
- cls.subnet = cls.create_subnet(cls.network)
-
@test.attr(type='negative')
@test.idempotent_id('4990b055-8fc7-48ab-bba7-aa28beaad0b9')
def test_router_create_tenant_distributed_returns_forbidden(self):
diff --git a/neutron/tests/tempest/config.py b/neutron/tests/tempest/config.py
index f50fa39..bad1000 100644
--- a/neutron/tests/tempest/config.py
+++ b/neutron/tests/tempest/config.py
@@ -12,6 +12,7 @@
from oslo_config import cfg
+from neutron import api
from tempest import config
@@ -22,7 +23,13 @@
cfg.BoolOpt('specify_floating_ip_address_available',
default=True,
help='Allow passing an IP Address of the floating ip when '
- 'creating the floating ip')]
+ 'creating the floating ip'),
+ cfg.BoolOpt('validate_pagination',
+ default=api.DEFAULT_ALLOW_PAGINATION,
+ help='Validate pagination'),
+ cfg.BoolOpt('validate_sorting',
+ default=api.DEFAULT_ALLOW_SORTING,
+ help='Validate sorting')]
# TODO(amuller): Redo configuration options registration as part of the planned
# transition to the Tempest plugin architecture